You are on page 1of 278

CUPRINS

Capitolul I
INTRODUCERE ÎN
ARHITECURA SISTEMELOR DE CALCUL............................... 1
1.1. Importanţa limbajului C....................................................1
1.2 Arhitectura de bază a unui calculator ................................4
1.2.1 Microprocesorul .........................................................6
1.2.2 Memoria .....................................................................8
1.2.3 Echipamentele periferice..........................................10
1.3. Programarea calculatorului .............................................13
1.3.1. Sistemul de operare .................................................14
1.3.2. Tipuri de fişiere .......................................................17
1.3.3. Construirea fişierului executabil .............................18

Capitolul II
REPREZENTAREA DATELOR ÎN CALCULATOR................. 23
2.1. Reprezentarea internă/externă a numerelor...........................24
2.2. Reprezentarea externă a numerelor ......................................25
2.2.1. Reprezentarea externă a numerelor întregi..............26
2.2.2. Reprezentarea externă a numerelor reale ................29
2.3 Reprezentarea internă a numerelor ..................................31
2.3.1. Reprezentarea internă a numerelor întregi ..............31
2.3.2 Adunarea, scăderea şi înmulţirea numerelor întregi.33
2.3.3 Reprezentarea internă a numerelor reale..................35
Game de reprezentare pentru numerele reale....................46
2.3.5. Codificare BCD.......................................................47

Capitolul III
ELEMENTELE DE BAZĂ ALE LIMABJULUI C...................... 49
3.1. Crearea şi lansarea în execuţie a unui program C...........49
3.2. Structura unui program C ...............................................51
3.3. Mulţimea caracterelor .....................................................53
I
3.3.1. Litere şi numere.......................................................53
3.3.2. Caractere whitespace...............................................53
3.3.3. Caractere speciale şi de punctuaţie..........................54
3.3.4. Secvenţe escape.......................................................54
3.4. Identificatori ...................................................................56
3.5. Cuvintele cheie ale limbajului C.....................................56
3.6. Constante ........................................................................56
3.6.1. Constante caracter ...................................................57
3.6.2. Constante întregi .....................................................57
3.6.3. Constante în virgulă mobilă ....................................57
3.6.4. Constante şir............................................................58
3.6.5. Constanta zero .........................................................59
3.6.6. Obiecte constante ....................................................59
3.6.7. Enumerări ................................................................60

Capitolul IV
OPERANZI ŞI OPERATORI ÎN C................................................ 61
4.1. Operanzi..........................................................................61
4.2. Operatori .........................................................................61
4.2.1. Operatori aritmetici .................................................62
4.2.2. Operatori de incrementare şi decrementare.............63
4.2.3. Operatori relaţionali ................................................63
4.2.4. Operatori logici .......................................................64
4.2.5. Operatori logici la nivel de bit.................................65
4.2.6. Operatorul de atribuire ............................................70
4.2.7. Operatorul sizeof .....................................................70
4.2.8. Operatorul ternar ? .................................................71
4.2.9. Operatorul virgulă ...................................................72
4.2.10. Operatorul de forţare a tipului sau de conversie
explicită (expresie cast) ........................................72
4.2.11. Operatorii paranteză ..............................................73
4.2.12. Operatorul adresă ..................................................73
4.2.13. Alţi operatori ai limbajului C ................................74
4.2.14. Regula conversiilor implicite şi precedenţa
operatorilor ...........................................................74

II
Capitolul V
INSTRUCŢIUNI .............................................................................. 76
5.1. Instrucţiuni etichetate (instrucţiunea goto) .....................76
5.2. Instrucţiuni expresie........................................................77
5.3. Instrucţiuni compuse.......................................................77
5.4. Instrucţiuni de selecţie ....................................................78
5.4.1. Instrucţiunea if.........................................................78
5.4.2. Instrucţiuni de selecţie multiplă: if - else if.............79
5.4.3. Instrucţiunea switch.................................................80
5.5. Instrucţiuni repetitive......................................................82
5.5.1. Instrucţiunea for ......................................................82
5.5.2. Instrucţiunea while ..................................................86
5.5.3. Instrucţiunea do-while.............................................87
5.5.4. Bucle încuibate........................................................89
5.5.5. Instrucţiunea break ..................................................91
5.5.6. Instrucţiunea continue .............................................92

Capitolul VI
TIPURI DE DATE STRUCTURATE ............................................ 93
6.1. Tablouri unidimensionale ...............................................93
6.1.1. Constante şir............................................................94
6.1.2. Iniţializarea vectorilor de caractere .........................95
6.1.3. Funcţii pentru prelucrarea şirurilor (fişierul antet
string.h).................................................................97
6.2. Tablouri cu două dimensiuni (matrice).........................100
6.2.1. Iniţializarea matricelor ..........................................100
6.2.2. Tablouri bidimensionale de şiruri .........................101
6.3. Tablouri multidimensionale..........................................101
6.4. Structuri ........................................................................102
6.4.1. Tablouri de structuri ..............................................104
6.4.2. Introducerea structurilor în funcţii ........................110
6.4.3. Tablouri şi structuri în structuri.............................114
6.5. Uniuni ...........................................................................114
6.6. Enumerări .....................................................................116

III
Capitolul VII
POINTERI ...................................................................................... 118
7.1. Operatori pointer...........................................................118
7.1.1. Importanţa tipului de bază.....................................120
7.1.2. Expresii în care intervin pointeri...........................120
7.2. Pointeri şi tablouri.........................................................125
7.2.1. Indexarea pointerilor .............................................126
7.2.2. Pointeri şi şiruri .....................................................128
7.2.3. Preluarea adresei unui element al unui tablou.......129
7.2.4. Tablouri de pointeri...............................................129
7.2.5. Pointeri la pointeri.................................................130
7.2.6. Iniţializarea pointerilor..........................................131
7.2.7. Alocarea dinamică a memoriei..............................132
7.2.8. Pointeri la structuri ................................................134
7.2.9. Structuri dinamice liniare de tip listă ...................137

Capitolul VIII
FUNCŢII ......................................................................................... 150
8.1. Forma generală a unei funcţii .......................................150
8.2. Reîntoarcerea dintr-o funcţie ........................................152
8.3. Valori returnate .............................................................153
8.4. Domeniul unei funcţii ...................................................154
8.4.1. Variabile locale .....................................................155
8.4.2. Parametri formali...................................................156
8.4.3. Variabile globale ...................................................157
8.5. Apelul funcţiilor............................................................161
8.6. Apelul funcţiilor având ca argumente tablouri .............163
8.7. Argumentele argc şi argv ale funcţiei main() ...............166
8.8. Funcţii care returnează valori neîntregi ........................168
8.9. Returnarea pointerilor...................................................169
8.10. Funcţii de tip void .......................................................172
8.11. Funcţii prototip ...........................................................173
8.12. Funcţii recursive .........................................................174
8.13. Clase de memorare (specificatori sau atribute)...........176
8.14. Pointeri la funcţii ........................................................181

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

Capitolul X
INTRĂRI/IEŞIRI ........................................................................... 194
10.1. Funcţii de intrare şi ieşire - stdio.h ...............................194
10.2. Operaţii cu fişiere .......................................................197
10.3. Nivelul inferior de prelucrare a fişierelor ...................199
10.3.1. Deschiderea unui fişier........................................200
10.3.2. Scrierea într-un fişier...........................................204
10.3.3. Citirea dintr-un fişier...........................................206
10.3.4. Închiderea unui fişier...........................................208
10.3.5. Poziţionarea într-un fişier....................................208
10.3.6 Ştergerea unui fişier .............................................210
10.3.7. Exemple de utilizare a funcţiilor de intrare/ieşire de
nivel inferior .......................................................211
10.4. Nivelul superior de prelucrare a fişierelor ..................216
10.4.1. Funcţia fopen() ....................................................216
10.4.2. Funcţia fclose()....................................................218
10.4.3. Funcţiile rename() şi remove()...........................219
10.4.4. Funcţii de tratare a erorilor..................................219
10.4.5. Funcţii cu acces direct .........................................220
10.4.6. Funcţii pentru poziţionare ...................................221
10.4.7. Ieşiri cu format ....................................................223
10.4.8. Intrări cu format ..................................................226
10.4.9. Funcţii de citire şi scriere a caracterelor..............228

Capitolul XI
UTILIZAREA ECRANULUI ÎN MOD TEXT ........................... 233
11.1. Setarea ecranului în mod text .....................................233
11.2. Definirea unei ferestre ................................................234
11.3. Ştergerea unei ferestre ................................................234
11.4. Deplasarea cursorului .................................................235
V
11.5. Setarea culorilor..........................................................235
11.6. Funcţii pentru gestiunea textelor ................................236

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

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

Capitolul XIV
ELEMENTE DE PROGRAMARE AVANSATĂ ....................... 257
14.1 Gestionarea memoriei ..................................................257
14.1.1 Memoria convenţională........................................257
14.1.2 Memoria expandată ..............................................260
14.1.3 Memoria extinsă...................................................260
14.1.4 Stiva......................................................................260
14.2 Servicii DOS şi BIOS ..................................................261
14.2.1 Serviciile BIOS ....................................................262
14.2.2 Serviciile DOS......................................................266
14.3 Bibliotecile C ...............................................................270
14.3.1 Reutilizarea unui cod obiect.................................270
14.3.2 Lucrul cu fişiere bibliotecă...................................270
14.3 Fişierele antet...............................................................271
BIBLIOGRAFIE ............................................................................ 272
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
calculatoare. În principiu, ele simplifică interacţiunea dintre programe
2
şi utilizatorii acestora. Astfel, diferite comenzi, date de intrare sau
rezultate pot fi exprimate simplu şi natural utilizând diferite standarde
care conţin ferestre, bare de meniuri, cutii de dialoguri, butoane, etc.
Cu ajutorul mouse-ului toate acestea pot fi accesate extrem de rapid şi
uşor fără a mai fi nevoie să cunoşti şi să memorezi o serie întreagă
comenzi ale sistemului de operare sau ale limbajului de programare.
Toate acestea conduc la interfeţe simple şi vizuale, accesibile unui
segment foarte larg de utilizatori.
Implementarea interfeţelor este mult simplificată prin utilizarea
limbajelor orientate spre obiecte, aceasta mai ales datorită posibilităţii
de a utiliza componente standardizate aflate în biblioteci specifice.
Importanţa aplicării conceptului de reutilizare a codului rezultă din
faptul că interfeţele utilizator adesea ocupă 40% din codul total al
aplicaţiei.
Firma Borland comercializează o bibliotecă de componente
standardizate care pot fi utilizate folosind limbajul C++, bibliotecă
cunoscută sub numele Turbo Vision.
De obicei, interfeţele utilizator gestionează ecranul în mod
grafic. O astfel de interfaţă utilizator se numeşte interfaţă utilizator
grafică. Una din cele mai populare interfeţe utilizator grafice pentru
calculatoarele IBM PC este produsul Windows oferit de firma
Microsoft.
Windows este un mediu de programare ce amplifică facilităţile
oferite de sistemul de operare MS-DOS. Aplicaţiile Windows se pot
dezvolta folosind diferite medii de dezvoltare ca: Turbo C++ pentru
Windows, Pascal pentru Windows, Microsoft C++, Microsoft Visual
Basic, Visual C şi Visual C++.
Componentele Visual permit specificarea în mod grafic a
interfeţei utilizator, a unei aplicaţii, folosind mouse-ul, iar aplicaţia
propriu-zisă se programează într-un limbaj de tip Basic, C sau C++.
Dacă în ani ’70 se considera că o persoană este rezonabil să se
poată ocupa de o aplicaţie de 4-5 mii de instrucţiuni, în prezent, în
condiţiile folosirii limbajelor de programare orientate pe obiecte,
această medie a ajuns la peste 25 de mii de instrucţiuni.
Un limbaj de programare trebuie privit nu doar la suprafaţa sa –
sintaxă şi mod de butonare a calculatorului pentru o implementare
particulară – ci mai ales în profunzime, prin conceptele pe care se
bazează, prin stilul de programare, prin modul de structurare a
aplicaţiei şi, implicit, a programului, prin filozofia de rezolvare a
3
problemelor folosind limbajul. Din aceste puncte de vedere, C-ul nu
poate lipsi din cultura unui programator, iar pentru un profesionist C-
ul este, şi mai mult, o necesitate vitală, acesta fiind piatra de temelie
pentru înţelegerea şi utilizarea eficientă a limbajelor de nivel înalt
orientate pe obiecte şi Visual.
1.2 Arhitectura de bază a unui calculator
Calculatoarele de tip PC (calculatoare personale) reprezintă cele
mai răspândite şi mai utilizate dintre calculatoare, datorită gradului de
accesibilitate şi preţului relativ scăzut. Indiferent de tipul
calculatorului, modul general de concepţie, de alcătuire şi funcţionare
este acelaşi. Calculatorul este o maşină programabilă. Două dintre
principalele caracteristici ale unui calculator sunt:
1. Răspunde la un set specific de instrucţiuni într-o manieră bine
definită.
2. Calculatorul poate executa o listă preînregistrată de instrucţiuni,
numită program.
Calculatoarele moderne sunt electronice şi numerice.
 Partea de circuite electrice şi electronice precum şi conexiunile
fizice dintre ele se numeşte hardware.
 Totalitatea programelor precum şi datele aferente acestor
programe poartă denumirea de software.
Echipamente de 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

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

5
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).
 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

6
operaţiile, iar cea de-a doua are funcţia de transfer a datelor de la şi
înspre microprocesor.
Microprocesorul reprezintă de fapt unitatea centrală a unui
calculator şi îndeplineşte o serie de activităţi specifice cum ar fi:
execută operaţii aritmetice şi logice, decodifică instrucţiuni speciale,
transmite altor cipuri din sistem semnale de control. Toate aceste
operaţii sunt executate cu ajutorul unor zone de memorie ale
microprocesorului, numite registre. Orice microprocesor are un set
finit de instrucţiuni pe care le recunoaşte şi pe care le poate executa.
Calculatoarele IBM PC folosesc procesoare INTEL sau
compatibile, realizate de alte companii cum ar fi: AMD, NexGen,
CYRIX. Numele microprocesorului este folosit la identificarea
calculatorului. Se folosesc frecvent expresii de tipul calculator 386,
calculator 486, calculator Pentium II, etc.
În 1971 firma Intel a fost abordata de o companie Japoneza,
acum dispărută, pentru a construi un circuit dedicat pentru un nou
calculator. Designerul Ted Hoff a propus o soluţie programabilă, de uz
general, şi astfel s-a născut circuitul Intel 4004. Au urmat la scurt timp
chipurile 4040 si 8008 dar lor le lipseau multe din caracteristicile
microprocesoarelor aşa cum le ştim noi azi. În 1974 Intel a prezentat
pentru prima oară circuitul Intel 8080 care a fost folosit in sistemele
Altair şi IMSAI. Curând după aceea au apărut procesoarele Motorola
6800 şi 6502 de la MOS Technology. Doi dintre proiectanţii de la
Intel au părăsit firma, creând corporaţia ZILOG care a produs chipul
Z80 (compatibil cu 8080 dar cu set de instrucţiuni mai puternic şi de
două ori mai rapid.
Cipul Intel 4004 a fost primul procesor comercial, lansat la
sfârşitul anului 1971. La un preţ de circa 200$ şi înglobând 2300 de
tranzistori, cipul 4004 dezvolta mai multă putere de calcul decât
ENIAC, primul calculator electronic, cu 25 de ani în urma. Faţă de
cele 18.000 de tuburi cu vacuum ce ocupau 900 metri cubi, procesorul
4004 putea dezvolta 60.000 de operaţii pe secundă. Această invenţie a
contribuit la revoluţionarea domeniilor de aplicaţii ale computerelor,
dând startul unui adevărat galop de inovaţii tehnologice. Următorul
pas a fost în 1980, când IBM a inclus un procesor Intel în arhitectura
primului PC.
Astăzi PC-urile sunt pretutindeni în jurul nostru. Un copil care
lucrează la o maşina ce incorporează un procesor Pentium Pro
beneficiază de mult mai multă putere de calcul decât dispunea
7
guvernul SUA în perioada lansării primelor echipaje umane către
Lună.
Într-un număr aniversar al publicaţiei Communications of the
ACM, Gordon Moore, co-fondator al companiei Intel, era optimist în
privinţa evoluţiei PC-urilor şi a microprocesoarelor: "complexitatea
microprocesoarelor, care se măsoară prin numărul de tranzistori pe
cip, s-a dublat aproape constant la fiecare 18 luni, de la apariţia
primului prototip 4004. Aceasta evoluţie exponenţială a determinat o
continuă creştere a performanţelor PC-urilor şi o scădere a costului
procesului de calcul. Pe când în 1991 un PC bazat pe procesorul Intel
486 costa aproape 225$ pentru o performanţă de un milion de
instrucţiuni pe secundă (MIPS), astăzi, un sistem desktop ce utilizează
un cip Pentium Pro este evaluat la circa 7$ pe MIPS. Nu se întrevede
nici o dificultate care să frâneze această rată de dezvoltare".
1.2.2 Memoria
Microprocesorul are capacitatea de a memora date care urmează
a fi prelucrate, cât şi rezultatele intermediare. Se observă că rolul său
principal este de a prelucra şi transmite informaţiile şi rezultatele şi
deci capacitatea sa de memorare este mică neputând stoca programe.
De aceea, un calculator necesită şi o memorie care să găzduiască date
şi programe.
Memoria este formată din punct de vedere fizic din cipuri ce
stochează informaţia sub forma a două niveluri de tensiune ce
corespund valorilor 0 şi 1 din sistemul de numeraţie. Celulele de bază
ale memoriei (ce pot avea valoarea 0 sau 1) se numesc biţi şi ele
reprezintă particulele cele mai mici de informaţie din calculator.
Pentru citirea informaţiilor nu se folosesc biţi în mod individual ci
aceştia sunt grupaţi într-o succesiune. Astfel o succesiune de 8 biţi
formează un octet (sau un byte) aceasta reprezentând unitatea de
măsură a capacităţii de memorie. Deoarece reprezentarea numerelor în
calculator se face în baza 2 şi nu în baza 10, aşa cum suntem obişnuiţi
în mod normal să lucrăm, şi multiplii unui byte vor fi puteri ale lui 2,
astfel:
1 KB=210B=1024 B
1 MB=210KB=1 048 576 B
1 GB=210MB=230 B
Abrevierile K (kilo), M (mega), G (giga) se scriu de obicei cu
litere mari şi reprezintă mii, milioane şi, respectiv, miliarde.
8
Memoria unui calculator are două componente: memoria
principală (internă) şi memoria secundară (externă). Memoria internă
este memoria ce poate fi accesată în mod direct de către microprocesor
şi în care sunt încărcate programele înainte de a fi executate de către
microprocesor. Dacă primele procesoare puteau accesa doar 1 MB de
memorie astăzi un procesor Pentium poate accesa peste 256 MB.
Memoria principală este formată din două tipuri de circuite: cip-uri
ROM şi cip-uri RAM.
Circuitele de tip ROM (Read Only Memory) au memorate
programele care controlează iniţial calculatorul (sistemul de operare).
Aceste memorii pot fi doar citite (conţinutul lor nu poate fi modificat).
Înscrierea conţinutului acestor memorii se face de către fabricant, iar
operaţiunea de înscriere cu programe se mai numeşte „arderea
memoriilor”.
Circuitele de tip RAM (Random Acces Memory ) sunt memorii
la care utilizatorul are acces şi al căror conţinut se şterge la
deconectarea calculatorului. În memoria RAM informaţia este stocată
temporar. De exemplu, programele de aplicaţii curente şi datele
asociate acestor aplicaţii sunt încărcate în memoria RAM înainte de a
fi prelucrate de către microprocesor.
Deoarece capacitatea de memorie a unui calculator nu poate fi
atât de mare încât să poată păstra toate programele pe vrem să le
executăm, a apărut necesitatea existenţei unor memorii externe, care
să fie solicitate la nevoie. Rolul acestora îl joacă discurile şi ele pot fi
asemănate cu cărţile dintr-o bibliotecă pe care le putem consulta ori de
câte ori avem nevoie de anumite informaţii.
Primele discuri apărute pentru PC-uri, numite şi dischete,
floppy disk-uri sau discuri flexibile, permiteau stocarea a maximum
160 KB de informaţie. Astăzi mai există doar dischete cu diametrul de
3.5 inch cu capacitatea de 1,44 MB. Existenţa acestora pare a fi pusă
însă în pericol de apariţia CD-urilor reinscriptibile a căror capacitate
de memorare depăşeşte 700 MB iar evoluţia tehnologică, din ce în ce
mai rapidă, dă semne că lucrurile nu se vor opri aici.
Dischetele folosesc metode magnetice de memorare a
informaţiei motiv pentru care ele se mai numesc şi suporturi
magnetice de informaţie.
Principala componentă a unui calculator utilizată pentru
memorarea programelor o reprezintă hard discul. Acesta poate fi
asemănat cu o dischetă de mare capacitate, integrată într-o unitate
9
încapsulată. Iniţial, puţine PC-uri prezentau hard discuri, dar cum
preţurile acestora au scăzut considerabil, iar performanţele şi
capacităţile au crescut, în prezent toate calculatoarele prezintă acest
dispozitiv. În clipa de faţă, capacitatea de memorare a unui hard disc a
depăşit valoarea de 40 de GB.
1.2.3 Echipamentele periferice
Comunicarea om-maşină se realizează cu ajutorul
echipamentelor periferice prin intermediul cărora utilizatorul poate
programa sau da anumite comenzi calculatorului sau poate vizualiza
rezultatele obţinute de către anumite programe. Principalele
echipamente periferice ale unui calculator sunt următoarele: tastatura,
mouse-ul, scanner-ul, monitorul şi imprimanta. Ele pot fi grupate în
echipamente de intrare – cele prin care calculatorul primeşte
informaţii sau comenzi (tastatură, mouse, scanner) - şi echipamente de
ieşire – cele prin care calculatorul transmite informaţii în exterior
(monitor, imprimantă). În continuare sunt prezentate câteva
caracteristici ale fiecărui echipament.
Tastatura – este principalul dispozitiv de intrare al
calculatorului prin intermediul căruia se transmit comenzi către
unitatea centrală. Cuplarea la calculator a tastaturii se face prin
intermediul unui cablu de conectare.
Din punct de vedere al dispunerii tastelor, tastatura se aseamănă
destul de mult cu cea a unei maşini de scris dar are şi părţi care o
individualizează.
Primele tastaturi au avut 83/84 de taste, pentru ca, ulterior, ele
să fie îmbogăţite prin dublarea tastelor existente sau adăugarea altora
noi, ajungându-se la 101/102 taste.
Din punct de vedere al funcţionalităţii lor ele pot fi împărţite în
patru categorii:
- taste alfanumerice;
- taste cu scopuri speciale;
- taste direcţionale şi numerice;
- taste funcţionale.
Tastele alfanumerice conţin literele, cifrele şi semnele de
punctuaţie şi ocupă partea centrală a tastaturii. Acţionarea unei astfel
de taste determină apariţia caracterului corespunzător pe ecranul
calculatorului. Tastele cu scopuri speciale sunt aşezate în acelaşi bloc
cu tastele alfanumerice şi determină efectuarea anumitor acţiuni fără
10
înscrierea de caractere pe ecran. Tastele de mişcare se află situate în
partea dreaptă a tastaturii şi ele funcţionează în două moduri, care pot
fi comutate prin acţionarea altei taste, aflate deasupra lor, pe care scrie
NumLock. Dacă ledul corespunzător acestei taste este aprins, modul
de lucru este numeric, în caz contrar fiind comutat pe semnificaţia
direcţională. Tastele funcţionale sunt un grup de 12 taste situate în
partea de sus a tastaturii având pe ele litera F urmată de un număr între
1 şi 12. Acţionarea acestor taste determină efectuarea unor operaţii
specifice de la program la program.
Mouse-ul – este tot un echipament de intrare mai uşor de
manevrat decât tastatura dar care poate efectua mai puţine operaţii.
Totuşi, foarte multe aplicaţii (în special aplicaţiile grafice) nu mai pot
fi concepute fără mouse. Un mouse are aspectul unei bucăţi de săpun,
uşor manevrabil, având dedesubt o bilă poziţionabilă, cu sensibilitate
şi viteză reglabile.
Mişcarea maouse-ului pe o suprafaţă plană este corelată cu
deplasarea pe ecran a unui cursor cu o formă deosebită: cruciuliţă,
săgeată, etc. Declanşarea unei acţiuni se face prin poziţionarea
cursorului în zona corespunzătoare şi apăsarea unuia dintre butoanele
aflate pe partea posterioară. Iniţial un mouse avea două sau trei
butoane. Acum există mouse-uri cu 5 butoane şi 2 rotiţe ce îndeplinesc
o serie de funcţii corespunzătoare unor taste speciale.
Folosirea mouse-ului uşurează mult munca utilizatorilor,
nemaifiind necesar ca aceştia să memoreze numărul relativ mare de
comenzi corespunzător fiecărui produs, ca în situaţia în care se
foloseşte numai tastatura. Utilitatea mouse-ului este şi mai evidentă în
cazul aplicaţiilor grafice. De altfel, WINDOWS este un sistem de
operare creat special pentru lucrul cu mouse-ul.
Scanner-ul – reprezintă dispozitive care se cuplează la un PC şi
cu care, prin intermediul unui software adecvat, se pot capta imagini,
fotografii, texte etc., în vederea unei prelucrări ulterioare. Astfel se pot
manevra imagini foto, se pot crea efecte grafice speciale, care nu se
pot obţine prin metode tradiţionale. După captare, imaginea poate fi
prelucrată, mutată, mărită, micşorată, rotită, colorată, umbrită,
suprapusă cu altă imagine etc.
Cu un software de recunoaştere optică a caracterelor datele sau
documentele tipărite pe coli de hârtie pot fi transformate în fişiere,
putându-se realiza chiar o stocare a lor sub formă de arhivă.

11
Monitorul - Sistemul video este format din două părţi: un
adaptor (placă) video şi un monitor sau display. Adaptorul video
reprezintă dispozitivul care realizează legătura (interfaţa) cu
calculatorul şi se află în interiorul acestuia. El va fi corespunzător
tipului de monitor video care îi este ataşat. Adaptorul video realizează
o rezoluţie orizontală şi una verticală. Rezoluţia reprezintă numărul de
elemente, în cazul de faţă puncte – pixeli – care pot fi afişate pe ecran.
De exemplu, un monitor VGA, în mod video, are o rezoluţie de 640 x
480 pixeli.
Standardul VGA (Video Graphics Array) a fost introdus de
către IBM o dată cu calculatoarele PS/2, iar modurile video VGA
reprezintă un superset al standardelor video anterioare, CGA (Color
Graphics Adapter) şi EGA (Enhanced Graphics Adapter). Cea mai
importantă îmbunătăţire adusă de standardul VGA a fost rezoluţia
superioară a caracterelor în modul text, precum şi posibilitatea de a
afişa 256 de culori la un moment dat.
Monitorul, denumit uneori şi display, permite vizualizarea
datelor introduse de la tastatură sau rezultate în urma execuţiei unor
comenzi sau programe, fiind încadrat în categoria echipamentelor
periferice de ieşire. Ca piesă principală, monitorul conţine un tub de
vacuum, similar cu cel de la televizor şi trei tunuri de electroni
(corespunzătoare celor trei culori fundamentale).
Standardele video MDA, CGA şi EGA folosesc monitoare
digitale. Datele care descriu culorile pixelilor sunt trimise de adaptorul
video la monitor sub forma unor serii de semnale digitale care sunt
echivalente unor serii de biţi.
Standardul VGA a introdus un nou tip de monitor care
utilizează semnale analogice pentru transferul informaţiilor privind
culoarea de la adaptorul video la monitor. Dacă semnalele digitale
prezintă niveluri care indică prezenţa sau absenţa unui bit, semnalele
analogice pot prezenta orice valoare între una minimă şi una maximă.
Imprimanta - Reprezintă un dispozitiv care poate fi ataşat unui
calculator, cu scopul tipăririi de texte şi grafică, putând fi considerată
un fel de maşină de scris automată. Până în prezent au fost realizate un
număr destul de mare de tipuri de imprimante pentru PC-uri, ele
diferind atât prin performanţe, cât şi prin modalităţile tehnice de
ralizare. Fiecare dintre ele prezintă avantaje şi dezavantaje, ideal fiind
a o folosi pe cea care corespunde cel mai bine tipului de lucrări

12
executate. În funcţie de modul în care este realizată imprimarea se
disting următoarele tipuri de imprimante:
- imprimante matriceale cu 9, 18 sau 24 de ace – realizează
imprimarea prin impactul acelor peste o bandă de hârtie;
- imprimante cu jet de cerneală – funcţionează prin pulverizarea
fină a unor picături de cerneală pe hârtia de imprimat;
- imprimante laser – ce utilizează o rază laser sau mici diode
luminiscente care încarcă electrostatic un tambur de imprimare,
corespunzător caracterului care urmează a fi imprimat;
- imprimante rapide de linii – ce imprimă mai multe linii odată,
fiind folosite mai mult la sisteme de calcul de dimensiuni mari.
Calitatea imprimării creşte de la primul la ultimul tip prezentat,
dar în mod corespunzător şi preţul echipamentului.
1.3. Programarea calculatorului
Programele de calculator, cunoscute sub numele de software,
sunt constituite dintr-o serie de instrucţiuni pe care le execută
calculatorul. Când se creează un program, trebuie specificate
instrucţiunile pe care calculatorul trebuie să le execute pentru a realiza
operaţiile dorite. Procesul de definire a instrucţiunilor pe care le
execută calculatorul se numeşte programare.
Programele executate pe un calculator pot fi împărţite în trei
categorii:
• programe de aplicaţie – sunt acele programe care
interacţionează direct cu utilizatorul, specializate în realizarea unei
categorii de prelucrări. Editoarele de texte, programele pentru
gestiunea bazelor de date, programele de tehnoredactare asistată de
calculator, de grafică etc. sunt programe de aplicaţie.
• utilitare – programe, care la fel ca programele de
aplicaţie, interacţionează direct cu utilizatorul, dar, spre deosebire de
acestea, realizează prelucrări de uz general. Utilitarele realizează o
serie de operaţii de „gospodărie” cum ar fi: copierea fişierelor,
pregătirea discurilor magnetice pentru utilizare, crearea de copii de
salvare, testarea echipamentului, etc.
• programe de sistem – realizează legătura între
componentele electronice ale calculatorului şi programele de aplicaţie
şi utilitare. Rolul programului de sistem este acela de a uşura sarcina
programatorului, simplificând îndeplinirea acelor sarcini care sunt

13
comune marii majorităţi a programelor de aplicaţie: alocarea
memoriei, afişarea caracterelor pe ecran şi la imprimantă, citirea
caracterelor de la tastatură, accesul la informaţiile stocate pe disc
magnetic, etc.
1.3.1. Sistemul de operare
Sistemul de operare este o parte componentă a software-ului
unui calculator, care mai cuprinde un număr variabil de programe
utilitare selectate conform cu necesităţile programatorilor.
Sistemul de operare este un program cu funcţii de coordonare şi
control asupra resurselor fizice ale calculatorului şi care intermediază
dialogul om-calculator. Sistemul de operare permite rularea
programelor şi păstrarea informaţiilor pe disc. În plus, fiecare sistem
de operare pune la dispoziţia aplicaţiilor o serie de servicii care permit
programelor să aloce memorie, să acceseze diferite echipamente
periferice, cum ar fi imprimanta, şi să gestioneze alte resurse ale
calculatorului.
Un sistem de operare trebuie să aibă capacitatea de a se adapta
rapid la modificările tehnologice, rămânând în acelaşi timp compatibil
cu hardware-ul anterior. Lanţul de comunicare utilizator – calculator
este prezentat în Figura 1.3:
Sistemul de operare este cel mai important program care rulează
pe un calculator. Orice calculator de uz general este dotat cu un sistem
de operare care permite execuţia altor programe. Sistemele de operare
execută operaţiuni de bază precum: recunoaşterea unei intrări de la
tastatură (preluare caracter), trimiterea unui caracter pentru afişare pe
ecranul monitorului, gestionarea fişierelor şi a directoarelor pe disc
(floppy-disk sau hard-disk), controlul fluxului de date cu
echipamentele periferice ca drivere de disc sau imprimante.

C A LC U LA T O R

S IS T E M D E
O PER AR E

A P LIC A Ţ II

U T ILIZ A T O R

Fig. 1.3. Comunicarea utilizator - calculator


14
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.
 multitasking: Permit mai multor programe să ruleze în acelaşi
timp (execuţie concurentă).

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

16
Indiferent de sistemul de operare utilizat, din punctul de vedere
al utilizatorului, informaţiile sunt scrise pe disc sub forma unor fişiere.
Un fişier este o colecţie de informaţii grupate sub acelaşi nume. Un
fişier poate fi un program executabil, un text, o imagine, un grup de
comenzi sau orice altceva.
Un fişier este identificat prin numele său. Numele unui fişier
este format dintr-un şir de caractere (care în funcţie de sistemul de
operare este limitat la un anumit număr maxim de caractere), urmate
eventual de semnul punct (.) şi de încă maximum 4 caractere, numite
extensie, ca de exemplu: nume.ext.
Pentru a putea avea acces rapid la fişiere, sistemul de operare
creează nişte fişiere speciale, numite directoare, care pot fi asemănate
cu cuprinsul unei cărţi, deoarece ele conţin numele fişierelor şi adresa
de început a acestora. De asemenea, un director poate conţine la
rândul său alte directoare creându-se astfel o structură arborescentă de
directoare în care poate fi găsit foarte repede un anumit fişier.
1.3.2. Tipuri de fişiere
Fişierele se pot împărţi în două categorii – executabile şi
neexecutabile. În prima categorie intră acele fişiere al căror nume scris
în dreptul prompterului (în cazul sistemului de operare DOS)
determină executarea unor activităţi de către sistemul de operare. O
parte dintre fişierele executabile sunt programe şi sunt recunoscute
prin extensia lor care poate fi EXE sau COM, altele fiind constituite
în fişiere de comenzi proprii sistemului de operare, a căror extensie
este BAT.
Fişierele COM, numite adesea şi comenzi, conţin informaţii în
formatul imagine de memorie. Ele sunt mai compacte şi mai rapide
decât fişierele EXE, dar lungimea lor nu poate să depăşească 64 K.
Fişierele EXE pot să ajungă la dimensiuni mai mari prin segmentarea
programului în fragmente a căror dimensiune să fie de maximum 64K.
Dintre fişierele neexecutabile vom aminti câteva mai
importante:
• fişiere text ;
• fişiere cu extensia SYS sau DRV, cunoscute sub numele de
driver-e şi care conţin instrucţiuni despre modul în care sistemul de
operare trebuie să controleze diferite componente hardware;
• surse de programe scrise în diferite limbaje (cu extensiile PAS –
limbajul Pascal, C – limbajul C, CPP – limbajul C++, etc.);
17
• fişiere care conţin informaţii intermediare între cele în limbaj
sursă şi cele executabile (extensiile OBJ, OVL);
• fişiere ce conţin imagini (extensiile JPEG, GIF, BMP);
• fişiere ce conţin sunete (extensiile WAV, MIDI, MP3) etc.
1.3.3. Construirea fişierului executabil
Instrucţiunile pe care le execută un calculator sunt de fapt
grupuri de 1 ş 0 (cifre binare) care reprezintă semnale electronice
produse în interiorul calculatorului. Pentru a programa primele
calculatoare (în anii 1940-1950), programatorii trebuiau să înţeleagă
modul în care calculatorul interpreta diferitele combinaţii de 0 şi 1,
deoarece programatorii scriau toate programele folosind cifre binare.
Cum programele deveneau din ce în ce mai mari, acest mod de lucru a
devenit foarte incomod pentru programatori. De aceea au fost create
limbaje de programare care permit exprimarea instrucţiunilor
calculatorului într-o formă mai accesibilă programatorului. După ce
programatorul scrie instrucţiunile într-un fişier - numit fişier sursă, un
al doilea program – numit compilator, converteşte instrucţiunile
limbajului de programare în şirurile 1 şi 0 – cunoscute sub numele de
cod maşină.
Pentru a obţine un program executabil, orice program sursă
trebuie eventual translatat (tradus) în limbaj cod maşină sau cod obiect
pe care îl poate înţelege microprocesorul. În urma acestui proces,
alături de fişierul sursă apare şi fişierul cod obiect (object file.)
Această translatare sau traducere este efectuată de către compilatoare,
interpretoare sau asambloare.
Compilatorul este folosit pentru transformarea codului sursă,
adică a programului scris într-un limbaj de programare de nivel înalt,
în cod obiect (object code). Acest cod obiect va fi transformat în faza
de editare de legături în cod maşină executabil de microprocesorul
sistemului de calcul.
Programatorii scriu programe într-o formă numită cod sursă.
Acest cod sursă parcurge apoi câţiva paşi înainte de a deveni program
executabil.
Pe scurt, un compilator este un program special care procesează
instrucţiuni scrise într-un limbaj de programare particular şi le
transformă în limbaj maşină sau cod maşină pe care îl poate executa
microprocesorul.

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

19
Fig. 1.5 Procesul de elaborare a unui program executabil
Procesul de obţinere a unui executabil este prezentat în figura
de mai jos. Blocurile tridimensionale reprezintă entităţile principale
ale mediului de programare: editorul de texte, compilatorul (compiler)
şi editorul de legături (linker). Blocurile dreptunghiulare reprezintă
fişierele rezultate în urma aplicării celor trei utilitare de sistem:
 în urma utilizării editorului de texte obţinem fişierul text sursă
cod cu numele generic “nume”. Dacă folosim limbajul de programare
C spre exemplu, se obţine fişierul nume.c care se va salva pe disc.
 în urma lansării în execuţie a compilatorului, acesta preia
fişierul sursă şi îl prelucrează corespunzător, semnalizându-se toate
erorile fatale pentru program sau avertismente utile programatorului în
procesul de depanare. În cazul în care compilarea se efectuează cu
succes, se obţine un fişier cod obiect, salvat pe disc sub numele
nume.obj
 în urma lansării în execuţie a editorului de legături, se preia
fişierul cod obiect nume.obj şi se leagă cu toate modulele necesare
(inclusiv funcţii de bibliotecă sau alte module externe), obţinându-se
un program executabil (cod maşină) cu numele nume.exe la care
adresele nu mai sunt simbolice ci absolute relativ la adresa de început
a programului. La lansarea în execuţie a programului fluxul de
informaţie este complet controlat de către microprocesor, toate
salturile de adresă fiind făcute corespunzător.
Interpretorul (interpreter) este un program care execută
instrucţiuni scrise într-un limbaj de nivel înalt. Numai anumite limbaje
20
de nivel înalt, spre exemplu BASIC, LISP sau MATLAB, sunt
prevăzute cu un interpretor.
Există două modalităţi de a executa un program scris în limbaj
de nivel înalt. Cel mai comun mod este acela de a compila programul.
Cealaltă modalitate este “pasarea” programului unui interpretor.
Un interpretor translatează instrucţiunile de nivel înalt într-o formă
intermediară care este apoi executată. Prin contrast, un compilator
translatează instrucţiunile de nivel înalt direct în limbaj maşină (cod
maşină). Programele compilate rulează în general mai rapid decât cele
interpretate. Un alt avantaj al programelor compilate este acela al
desprinderii din context în sensul că programele executabile generate
în urma procesului de compilare pot fi executate direct sub sistemul de
operare al calculatorului. Un program interpretat se execută sub
mediul în care a fost creat.
Spre exemplu, pentru a rula un program scris în limbajul
BASIC se lansează în execuţie mediul BASIC, apoi se deschide
fişierul sursă-BASIC corespunzător şi se lansează interpretorul de
BASIC pentru execuţia sa.
Avantajul unui interpretor este acela al evitării procesului de
compilare consumator de timp în cazul în care avem programe de mari
dimensiuni. Interpretorul poate executa imediat programele sursă.
Pentru acest motiv interpretoarele se folosesc mai ales în procesul de
dezvoltare al programelor, când programatorul doreşte adăugarea unor
mici porţiuni de program pe care să le testeze rapid. De asemenea,
interpretoarele permit o programare interactivă fiind des folosite în
procesul de instrucţie.
În mediul de programare MATLAB, mediu interpretor, orice
comandă utilizator se execută imediat. Se pot edita şi fişiere script,
care conţin secvenţe de comenzi care se execută secvenţial.
Programele de descriere a paginii (Page Description Languages)
ca PostScript spre exemplu folosesc un interpretor. Fiecare
imprimantă PostScript are incorporat un interpretor care execută
instrucţiuni PostScript.
Asamblorul (assembler) este un program care face translaţia
unui program scris în limbaj de asamblare (limbaj de nivel scăzut,
corespunzător microprocesorului sistemului de calcul) în limbaj cod
maşină. Putem spune că asamblorul reprezintă pentru limbajul de
asamblare ceea ce reprezintă compilatorul pentru limbajele de nivel
înalt. Cum limbajul de asamblare conţine instrucţiuni mai puţin
21
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).
I n s tr u c ţiu n ile în lim b a ju l
d e n iv e l în a lt s e in tr o d u c
d e la ta s ta tu r ã .

T o t c e s e in tr o d u c e d e la
ta s ta tu r ã e s te v izib il p e m o n ito r

E d it o r d e t e x t e
( e v e n tu a l in c o r p o r a t în m e d iu )

F iş ie r te x t ( f iş ie r s u rs ã )
c u e x te n s ia a d e c v a tã :
n u m e .p a s ( lim b a j P a s c a l)
n u m e .c ( lim b a j C )
n u m e .c p p ( lim b a j C + + )
n u m e .b a s ( lim b a j B A S I C ) , e t c . F iş ie r u l s u r s ã c u n u m e le
“n u m e ” ş i e x te n s ia
c o r e s p u n zã to a r e s e s a lv e a zã
d in m e m o r ia R A M p e h a r d d is k
C o m p ila t o r
S e c o m p ile a zã fiş ie r u l s u r s ã

S e o b ţin e fiş ie r u l c o d o b ie c t :
n u m e .o b j
S e s a lv e a zã p e h a r d d is k fiş ie r u l
n u m e .o b j

L in k - e d it a re
( le g a r e a tu tu r o r m o d u le lo r n e c e s a r e )

S e o b ţin e fiş ie r u l c o d m a ş in ã
( e x e c u t a b il) :
n u m e .e x e S e s a lv e a zã p e h a r d d is k fiş ie r u l
n u m e .e x e

L a n s a r e a în e x e c u ţie d e c ã tr e
s is te m u l d e o p e r a r e
a e x e c u ta b ilu lu i n u m e . e x e

Fig. 1.6 Detalierea procesului de generare a unui executabil


22
Capitolul II

REPREZENTAREA DATELOR ÎN
CALCULATOR

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


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

H igh= ’1 ’

L o w = ’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.

23
În altă ordine de idei, dacă pentru reprezentarea externă sunt
semnificative simbolurile de reprezentare (cifre, semnele + sau -,
punct zecimal sau binar, mantisă sau exponent), pentru reprezentarea
internă sunt necesare convenţii de reprezentare: indiferent de tipul
datelor, acestea vor fi colecţii sau şiruri de cifre binare 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
24
- cum se reprezintă intern un număr natural
- cum se reprezintă extern un număr întreg negativ
- cum se reprezintă intern un număr întreg negativ
- cum se face conversia de la reprezentarea externă la cea
internă
- cum se face conversia de la reprezentarea internă la cea
externă
2.2. Reprezentarea externă a numerelor
În ceea ce priveşte reprezentarea externă, nu sunt nici un fel de
dificultăţi deoarece fiecare este familiarizat cu reprezentarea zecimală
a numerelor naturale sau reale. Trebuie menţionat de la început că
orice tip de reprezentare pe care o vom folosi este ponderală în sensul
că poziţia cifrelor în număr nu este întâmplătoare ci conformă cu o
pondere corespunzătoare unei puteri a bazei de numeraţie.
O caracteristică a reprezentărilor externe este folosirea unor
convenţii de format unanim acceptate şi de altfel foarte naturale pentru
un utilizator uman. Spre exemplu, pentru a exprima numere negative
se foloseşte semnul “-” iar pentru reprezentarea numerelor reale se
foloseşte punctul “.” pentru delimitarea părţii întregi de cea
fracţionară. De asemenea, suntem familiarizaţi şi cu notaţia ştiinţifică
în care intervine mantisa şi exponentul (în virgulă mobilă).
Reprezentarea zecimală este cea mai naturală pentru utilizatorul
uman. Vom oferi în continuare câteva exemple de reprezentări
zecimale externe:
Număr Reprezentare Reprezentare
normală ştiinţifică
2
37 37 0.37x10
2
-37 -37 -0.37x10
0
0.375 0.375 0.375x10
0
-0.375 -0.375 -0.375x10
-2
0.00375 0.00375 0.375x10
-2
-0.00375 -0.00375 -0.375x10
2
12.375 12.375 0.12375x10
2
-12.375 -12.375 -0.12375x10
Î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

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

26
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:
n −1
( ) ∑ ck ⋅ b k
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

27
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 1 1 0 0 1
62 1 1 1 1 1 0
Se observă cum se obţine rezultatul corect.
Înmulţirea se face în mod 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 4 A11 î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

28
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:
n−1
( ) ∑ck ⋅bk
N10 = ± cn−1bn−1 +cn−2bn−2 +c1b1 +c0b0 +c−1b−1 +c−2 ⋅ b−2+c−m+1b−m+1 +c−mb−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ă:
(
N2 =±bmbm−1...b1b0.b−1b−2...b−n =bm2m +bm−12m−1 +b121 +b020 +b−12−1 +b−22−2 +...+b−n2−n )
Spre exemplu, numărul 12.25 va avea reprezentarea binară:
12.2510 = 1100.01 = 2 3 + 2 2 + 2 −2

29
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.010111101...2
Cu cât mai multe cifre binare vom reţine după punctul binar, cu atât
vom fi mai aproape de valoarea exactă 5.37.
Obţinem un rezultat foarte important: Deşi un număr zecimal
poate avea un număr finit de cifre zecimale după punctul zecimal,
reprezentarea sa binară internă poate avea un număr infinit de cifre
binare. Este valabilă şi reciproca: un număr real zecimal cu un număr

30
infinit de cifre se poate reprezenta într-o altă bază pe un număr finit
1
de cifre ( ex: = 0.3333...3...10 = 0.13 ). Cum orice reprezentare
3
binară internă este pe un număr finit de biţi, numărul poate să nu fie
reprezentat exact în calculator, ci cu o anumită aproximaţie. Acest
lucru este decisiv pentru a înţelege importanţa lungimii reprezentării
numerelor în calculator. Cu cât un număr binar se reprezintă pe un
număr mai mare de biţi, cu atât precizia de reprezentare creşte.
2.3 Reprezentarea internă a numerelor
Deoarece semnalul intern purtător de informaţie într-un
calculator este de tip binar, un număr zecimal (întreg sau real) se va
reprezenta intern în baza 2 cu ajutorul unui număr binar. O cifră binară
se numeşte bit (Binary Digit) şi poate fi fie 0 fie 1.
În reprezentarea externă a numerelor am văzut că se poate folosi
orice bază de numeraţie (cu cifrele corespunzătoare). De asemenea,
numerele pot fi prefixate cu un simbol de semn ± şi pot include în
reprezentare şi punctul de separaţie între partea întreagă şi cea
fracţionară.
În reprezentarea internă acest lucru nu mai este posibil deoarece
semnele plus (+), minus (-) sau punct (.) nu au nici o semnificaţie
pentru calculator. Orice număr (orice tip de dată) este reprezentat la
nivel intern de un număr prestabilit de biţi. Specialiştii din industria
software au ajuns la un consens de reprezentare concretizat prin
standardul IEEE 754 de reprezentare a internă a numerelor reale în
computere.
Reprezentarea internă a numerelor a impus în limbajul C
definirea aşa-numitelor tipuri de date.
Tipul unei date reprezintă modul în care microprocesorul
stochează în memorie şi prelucrează cu ajutorul regiştrilor interni o
dată. Tipul unei date se referă la lungimea sa de reprezentare (pe câţi
biţi se reprezintă data) precum şi ce semnificaţie au anumite câmpuri
de biţi din cadrul reprezentării.
2.3.1. Reprezentarea internă a numerelor întregi
Un număr binar este o colecţie de cifre binare ponderate fiecare
cu o putere a lui 2. Bitul corespunzător ponderii celei mai mari, situat
cel mai în stânga, se numeşte MSB (Most Significand Bit) iar cel

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

32
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

37
 10 = 100101 2 = 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

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


0011001 +
− 37 = 1011011
 1011011
25 = 0110012 = 0011001
−−−−−−
1110100 = −64 + 52 = −12
33
Î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] = [− 128, 127].
7 7

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

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

35
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
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
36
reprezentare, adică să determinăm magnitudinea (valorile minime şi
maxime pozitive şi negative) ce pot fi reprezentate în formatul
respectiv.
În limbajul C, există două tipuri de reprezentare pe care le
putem numi principale: tipul întreg şi tipul real, fiecare având şi
anumite particularizări. Astfel, tipul întreg (int) include şi tipul
caracter (char) iar tipul real (float) include şi tipul real extins
(double).
Tipurile de date le vom reprezenta de la simplu la complex, în
ordinea char, int, float, double.
Tipurile de bază sunt char, int, float, double şi cu ajutorul
modificatorilor de tip putem obţine diverse particularizări.
Modificatorii pot fi signed, unsigned, short, long.
Ca o generalitate, numerele sunt reprezentate intern luându-se în
considerare bitul de semn, deci implicit numerele întregi sau reale au
MSB bit de semn. Dacă se specifică explicit, prin modificatorul
unsigned, nu se mai consideră (interpretează) bitul de semn.
2.3.3.1 Tipul char
Codul ASCII (American Standard Code for Information
Interchange) este un cod de reprezentare a caracterelor. Prin caracter
înţelegem unităţile de bază care se pot tasta (intrări de la tastatură),
tipări la imprimantă sau afişa pe ecran. Tastatura reprezintă, de
exemplu, dispozitivul de intrare care conţine de fapt o întreagă
colecţie de caractere ce pot fi emise prin apăsarea unei taste. Pentru a
fi receptat, emis sau prelucrat de către calculator, fiecare caracter are
asociat un cod binar (o combinaţie de biţi) care îl identifică în mod
unic. Cum cu un octet putem codifica 28 = 256 caractere, octetul s-a
dovedit o entitate suficientă pentru codificarea caracterelor utilizate în
informatică. În 256 de coduri distincte se pot include literele mari şi
mici ale alfabetului anglo-saxon (inclusiv litere specifice diverselor
alfabete precum cel chirilic sau particularităţi ale diferitelor ţări: ş, ţ, â,
î, Ş... în română, de exemplu). Se mai pot include caractere ce
reprezintă numere, semne de punctuaţie sau alte caractere de control.
Codul ASCII a standardizat această codificare, astfel încât el este
folosit în cvasitotalitatea calculatoarelor (doar mainframe-urile IBM
mai folosesc un alt cod, mai vechi, numit EBCIDIC). Dacă se declară
o dată de tip char, ea este considerată explicit de tipul signed char (cu
MSB bit de semn), deci reprezentarea internă este de forma:
37
S b6 b5 b4 b3 b2 b1 b0

B it d e s e m n
Gama de reprezentare este cuprinsă între
max = 27 − 1 = 127
 ⇒ [− 128, 127]
min = −27 = −128
Dacă se declară tipul unsigned char, atunci nu se mai consideră
(interpretează) bitul de semn şi data se consideră întreagă pozitivă, în
gama
max = 2 8 − 1 = 255
 ⇒ [0, 255]
min = 0
Tabelele de mai sus conţin codurile ASCII ale primelor 128 de
caractere. Coloana D semnifică valoarea zecimală (decimal) a
octetului, coloana H reprezintă aceeaşi valoare reprezentată în format
hexazecimal (baza 16) iar în coloana Sym se reprezintă simbolul afişat
pe monitoarele PC.
Întregul alfabet al limbajului C se regăseşte în mulţimea
primelor 128 de caractere ASCII. Restul de 128 de caractere se mai
numeşte şi set de caractere extins ASCII şi poate fi vizualizat printr-un
program simplu.
Trebuie menţionat faptul că reprezentarea datelor în format
hexazecimal este foarte răspândită în tehnica programării
calculatoarelor. Avantajul reprezentării interne a datelor în format
hexazecimal constă în folosirea unui număr mai mic de cifre (de 4 ori
mai mic decât numărul de cifre binare).
Reprezentarea unui număr natural în format hexazecimal se
realizează cu metoda împărţirii succesive la 16 sau, mai simplu,
pornind de la reprezentarea binară a numărului.
Cum mulţimea cifrelor hexa conţine 16 simboluri (0…9 şi
A…F), pentru codificarea celor 16 cifre avem nevoie de 4 cifre binare
( 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

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

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

2.3.3.2 Tipul int


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

S b30 O ctetul 1
O ctetul 2
O ctetul 3
b0 O ctetul 4

LS B
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 = 2 31 − 1 3 3
 ( ) ( )
; 2 31 = 2 ⋅ 2 30 = 2 ⋅ 210 ≅ 2 ⋅ 10 3 ≅ 2 ⋅ 10 9
 min = − 2 31

40
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 = 2 32 − 1 3 3

 min = 0
( ) ( )
; 2 32 = 4 ⋅ 2 30 = 4 ⋅ 210 ≅ 4 ⋅ 10 3 ≅ 4 ⋅ 10 9

Gama de reprezentare se poate schimba cu ajutorul


modificatorilor short sau long.
MSB

S b 14
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
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

41
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)
M SB

b3 1 b3 0

b0

LSB

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

42
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
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

43
Exponent = 10000010 = 13010
Significand = 1001000…00
Valoarea reală a exponentului va fi 130 - 127 = 3
Biţii câmpului significand se obţin adăugând MSB phantom, deci
aceştia vor fi 11001000...00
Numărul real care s-a stocat este:
0.110010...00 x 24 = 1100.1 =12.5
Reprezentarea internă a numărului 12.5, pe 4 octeţi (float), este
următoarea:
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):
12 .5 10 = 41480000 16
Î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 . 5 10 = C 1480000 16
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
44
- exponentul nu va mai fi pe 8 biţi ca la tipul float, ci pe 11 biţi,
deci se va schimba şi bias, care va fi 1023. Atunci:
3 + 1023 = 1026 = 1024 + 2 = 1000000001 0
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 = 4029000000 000000 16


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:

45
.10101 ×
5.2510 = 101.012 = .10101× 23 .11
 −−−−−
 1
1.510 = 1.12 = .11 × 2 ; 10101
 3+1
5.25 × 1.5 = [(.10101) × (.11)]× 2 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.
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:
210 = 1024 ≅ 1000 = 103 este n max = 3.4028 ⋅ 10 38
exponent_ biasmin = 0 ⇒ exponent_ realmin = 0 − 127 = −127
−12 −12 −13
( )
nmin = 2−127 = 2−7 ⋅ 210 ( )
= 23 ⋅ 2−10 ⋅ 210 ( )
= 8 ⋅ 210 ≅ 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
102
( )
nmax = 21024 = 24 ⋅ 210 = 16 ⋅ (1024)306 ≅ 16 ⋅ 10306 = 1.6 ⋅ 10307

Valoarea maximă exactă este n max = 1.7 ⋅10 308

46
exponent _ biasmin = 0 ⇒ exponent _ realmin = 0 − 1023 = −1023
−102
( )
nmin = 2−1023 = 2−3 ⋅ 210 ≅ .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
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.

47
Ca o concluzie la acest capitol, decisiv pentru înţelegerea
dezvoltărilor ulterioare, putem sintetiza următoarele:
 Reprezentarea externă a numerelor se referă la modul în care
operatorul uman acceptă schimbul de date cu calculatorul. Acest
schimb de date are dublu sens: de la operatorul uman către calculator
şi invers.
Reprezentarea externă este de obicei zecimală şi are un format aproape
identic cu formatul matematic uzual: simbol de semn prefixat, punct
zecimal, mantisă sau exponent. Numerele naturale se mai pot
reprezenta şi în format octal sau hexazecimal. În format extern se
introduc datele de la tastatură pentru prelucrare şi se obţin pe monitor
sau la imprimantă rezultatele oferite de calculator.
 Reprezentarea internă a numerelor se referă la modul în care
se stochează datele în memoria RAM a calculatorului şi respectiv în
regiştrii interni ai microprocesorului. Această reprezentare internă este
legată de noţiunea de tip de dată.
 Tipul de dată întreg (integer) se reprezintă intern pe 2, 4 sau 8
octeţi în complement faţă de 2, cu cel mai semnificativ bit (MSB) bit
de semn: 1 pentru numere întregi negative şi 0 pentru numere întregi
pozitive. Un caz particular de dată de tip întreg este tipul character,
interpretat ca întreg pe un octet.
 Tipul de dată real (float) se reprezintă intern pe 4, 8 sau 10
octeţi şi conţine 3 câmpuri de biţi distincte: bit de semn, câmp mantisă
şi câmp exponent, de lungimi corespunzătoare.
Dacă se specifică explicit, toate numerele se pot defini fără semn
(unsigned), caz în care calculatorul nu mai interpretează bitul de semn
(MSB) diferit ci îl include în câmpul de reprezentare al mărimii,
crescând gama de reprezentare.

48
Capitolul III

ELEMENTELE DE BAZĂ ALE


LIMABJULUI C

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


program C
Prezentăm câteva comenzi simple pentru a lansa în execuţie un
program C folosind compilatorul BORLANDC v3.1 (versiunea
pentru sistemul de operare DOS):
• după setarea pe directorul corespunzător se tastează
- bc – pentru a intra în mediul BorlandC;
- <Alt>-F – pentru a selecta meniul File;
- N – pentru a deschide un fişier nou.
• se editează programul sursă folosind editorul mediului
BorlandC;
Exemplu:
#include <stdio.h>
void main (void)
{
printf("Primul program in C!");
}
• se acţionează tasta F2 şi se indică numele fişierului (cu
extensia .c sau .cpp) pentru salvarea programului sursă pe disc – de
exemplu mesaj.c - (se recomandă salvarea pe disc după efectuarea
oricărei modificări în programul sursă pentru evitarea pierderii
accidentale a acesteia);
• se realizează compilarea, link-editarea (realizarea
legăturilor) şi lansarea în execuţie a programului executabil mesaj.exe
tastând <CTRL>-F9;
• pentru a vizualiza rezultatele execuţiei programului se
tastează <Alt>-F5;
• se revine în fereastra de editare a mediului acţionând o tastă
oarecare;
• pentru a închide un fişier sursă se tastează <Alt>-F3 iar
pentru a ieşi din program în mediul de operare se tastează <Alt>-X.
49
La fel de simplu poate fi utilizată şi varianta pentru sistemul de
operare WINDOWS a compilatorului BORLANDC v3.1:
• se lansează în execuţie programul bcw.exe;
• se selectează din meniul File opţiunea New, creându-se
fişierul noname00.cpp;
• în fereastra noname00.cpp se introduce codul programului;
• din meniul File se selectează opţiunea Save As… sau Save
iar în căsuţa de dialog care apare se va salva fişierul program cu
extensia .cpp;
• din meniul Compile se selectează opţiunea Build All ce va
afişa caseta de dialog Compiling (compilare);
• dacă operaţia de compilare se încheie cu succes (nu există
erori de sintaxă în program) compilatorul va afişa mesajul Press any
key, caz în care compilatorul va crea fişierul executabil;
• lansarea în execuţie a fişierului executabil se poate realiza
folosind opţiunea Run din meniul Run sau combinaţia de taste
<CTRL>-F9.
Fiecare limbaj de programare are un set de reguli, denumite
reguli sintactice, reguli ce trebuie respectate la editarea unui cod
sursă. Dacă este încălcată o regulă sintactică, programul nu va fi
compilat cu succes. În acest caz, pe ecran va fi afişat un mesaj de
eroare ce specifică linia ce conţine eroarea, precum şi o scurtă
descriere a erorii. În exemplul următor programului îi lipseşte
caracterul punct şi virgulă după utilizarea funcţie printf:
#include <stdio.h>
void main (void)
{
printf("Primul program in C!")
}
La compilare pe ecran vor apare următoarele mesaje de eroare:
Compiling NONAME00.CPP:
Error NONAME00.CPP 5: Statement missing ;
Error NONAME00.CPP 5: Compound statement missing }
Cu toate că în codul sursă există doar o eroare, compilatorul de
C va afişa două mesaje de eroare. Lipsa caracterului punct şi virgulă
provoacă o serie de erori în cascadă. Pentru a corecta erorile sintactice
se merge cu ajutorul cursorului în linia indicată de către mesajul de
eroare şi se corectează instrucţiunea respectivă.

50
3.2. Structura unui program C
Conceptul de bază folosit în structurarea programelor scrise în
limbajul C este funcţia. Astfel, un program în C este compus din cel
puţin o funcţie şi anume funcţia main() sau funcţia principală. La
rândul ei, funcţia main() poate apela alte funcţii definite de utilizator
sau existente în bibliotecile ce însoţesc orice mediu de dezvoltare de
programare în C. Structura generală a unei funcţii C este de forma:
tip nume_funcţie (param_1, param_2, ...,param_n)
− − − − − − − −
 Instrucţiuni declarare tip parametri
− − − − − − − −
− − − − − − − −
{
− − − − − − −
 Corp funcţie=secvenţă de instrucţiuni sau apel de funcţii
− − − − − − −
− − − − − − −
}
unde: - tip reprezintă tipul de dată returnat de funcţie (în mod implicit
o funcţie returnează tipul int);
- nume_funcţie reprezintă numele sub care funcţia este
cunoscută în program;
- param_1,...,param_n - parametrii cu care funcţia este apelată
şi al căror tip poate fi declarat direct în această listă, sau prin
instrucţiuni separate plasate imediat după lista parametrilor.
Corpul funcţiei este definit ca o secvenţă de instrucţiuni şi/sau
apeluri de funcţii şi este delimitat de restul funcţiei prin paranteze
acolade.
În limbajul C există două categorii de funcţii. O primă categorie
este formată de funcţiile ce returnează o valoare la revenirea din ele în
punctul de apel, tipul acestei valori fiind definit de de tipul funcţiei.
Cealaltă categorie conţine funcţiile ce nu returnează nici o valoare la
revenirea din ele , pentru aceste funcţii fiind utilizat cuvântul cheie
void în calitate de tip. El semnifică lipsa unei valori returnate la
revenirea din funcţie.
Exemple:
1) void f(void)
{
…………
}

51
Funcţia f nu are parametri şi nu returnează nici o valoare.
2) double g(int x)
{
…………
}
Funcţia g are un parametru x de tipul int şi returnează la
revenirea în programul principal o valoare flotantă în dublă precizie.
Funcţiile C sunt în general unităţi independente, compilabile
separat. Instrucţiunile, la rândul lor, pot defini tipul unor date folosite
în program, sau operaţii ce trebuie executate prin program.
Din punct de vedere sintactic, orice instrucţiune trebuie
terminată cu caracterul ";", iar grupurile de instrucţiuni pot fi
delimitate prin caracterele { şi } pentru a forma unităţi sintactice noi
de tip bloc. Funcţiile apelate vor primi valori pentru argumentele
(parametrii) lor şi pot returna către funcţia apelantă valori de un
anumit tip.
Cu aceste precizări generale, dacă avem un program
compus din două funcţii, şi anume funcţia principală şi o funcţie
apelată f(), atunci structura acestuia va fi de forma:
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.

52
3.3. Mulţimea caracterelor
În programele C pot fi utilizate două mulţimi de caractere:
mulţimea caracterelor C şi mulţimea caracterelor C reprezentabile.
Mulţimea caracterelor C se compune din litere, cifre, semne de
punctuaţie care au o semnificaţie specifică pentru compilatorul C.
Programele C sunt formate din combinaţii ale caracterelor din
mulţimea de caractere C constituite în instrucţiuni semnificative.
Mulţimea caracterelor C este o submulţime a mulţimii caracterelor C
reprezentabile. Mulţimea caracterelor reprezentabile este formată din
totalitatea literelor, cifrelor şi simbolurilor grafice. Dimensiunea
mulţimii de caractere reprezentabile depinde de tipul de terminal,
consolă etc. Fiecare caracter din mulţimea caracter din mulţimea
caracterelor C are un înţeles explicit pentru compilatorul C.
Compilatorul dă mesaje de eroare când întâlneşte caractere
întrebuinţate greşit sau caractere care nu aparţin mulţimii caracterelor
C. În continuare sunt descrise caracterele şi simbolurile din mulţimea
caracterelor C şi utilizarea acestora.
3.3.1. Litere şi numere
Mulţimea caracterelor C include literele mari şi mici ale
alfabetului englez şi cifrele zecimale din sistemul de numere arabe.
Literele mari şi mici ale alfabetului englez sunt următoarele:
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
53
drept componente de constante, sau ca şiruri de caractere. Caracterele
whitespace sunt utilizate pentru a face programele mai lizibile.
Comentariile sunt de asemenea tratate ca whitespace.
3.3.3. Caractere speciale şi de punctuaţie
Caracterele speciale şi de punctuaţie din mulţimea
caracterelor C sunt folosite pentru mai multe scopuri. Tabelul următor
prezintă aceste caractere.
Aceste caractere au o semnificaţie specială pentru compilatorul
de C. Caracterele de punctuaţie din setul de caractere reprezentabile C
care nu apar în acest tabel pot fi utilizate numai în şiruri, constante
caracter şi comentarii.
Caracter Nume Caracter Nume
, Virgulă ! Semnul
exclamării
. Punct | Bară verticală
; Punct şi virgulă / Slash
: Două puncte \ Backslash
? Semnul ~ 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

54
acţiuni precum carriage return şi tab pe terminale şi imprimante şi
pentru a furniza reprezentarea caracterelor care normal au înţeles
special, cum ar fi ghilimelele (”). O secvenţă escape constă dintr-un
backslash urmat de o literă sau combinaţii de cifre. Setul complet de
secvenţe escape cuprinde:
\a caracterul BEL - activare sunet
\b caracterul BS (backspace) - revenire cu un spaţiu
\f caracterul FF (form feed) - salt de pagină la imprimantă
\n caracterul LF (line feed) - rând nou
\r caracterul CR (carriage return) - revenire la coloana 1
\t caracterul HT (horizontal tab) - tab orizontal
\v caracterul VT (vertical tab) - tab vertical
\\ caracterul \ (backslash)
\" caracterul " (double qoute) - ghilimele
\' caracterul ' (single qoute) - apostrof
\0 caracterul NULL
\ooo - constantă octală
\xhh - constantă hexazecimală
Backslash-ul care precede un caracter neinclus în lista de mai
sus este ignorat şi acest caracter este reprezentat ca un literal. De
exemplu, forma „\c” reprezintă caracterul c într-un literal sau într-o
constantă caracter. Secvenţele \ooo şi \xdd permit scrierea oricărui
caracter din setul ASCII ca un număr octal format din trei cifre sau ca
un număr hexagesimal format din două cifre.

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


'\60' '\x30' 48 ASCII
'\137' '\x5f' 95 ASCII
Numai cifrele octale (de la 0 la 7) pot apare într-o secvenţă
escape octală şi trebuie să apară cel puţin o cifră. De exemplu,
caracterul backspace poate fi scris ca „\10” în loc de „\010”.
Similar, o secvenţă hexagesimală poate să conţină cel puţin o
cifră, iar a doua cifră poate fi omisă. Totuşi, când se utilizează
secvenţe escape în şiruri, este indicat să se scrie toate cele trei cifre ale
secvenţei. Altfel, caracterul care urmează după secvenţa escape ar
putea fi interpretat ca o parte a secvenţei, dacă se întâmplă să fie o
cifră octală sau hexagesială. De exemplu, secvenţa \0331 este
interpretată drept ESC şi 1. Dacă am scrie \331, omiţând primul zero,
atunci am avea o interpretare greşită.
55
Secvenţele escape permit caractere de control negrafice pentru a
fi transmise către display. Caracterele negrafice trebuie totdeauna
reprezentate ca secvenţe escape. Plasând necorespunzător un caracter
negrafic în programe C, el are rezultat imprevizibil.
3.4. Identificatori
Identificatorii sunt nume ce sunt date variabilelor, funcţiilor şi
etichetelor utilizate în program. Un nume este o succesiune de litere şi
eventual cifre, primul caracter fiind literă. În calitate de litere se pot
utiliza literele mici şi mari ale alfabetului englez, precum şi caracterul
subliniere (_). Numărul de caractere care intră în componenţa unui
nume nu este limitat. Numele sunt utilizate pentru a defini diferite
variabile sau funcţii într-un program C.
În mod implicit, numai primele 32 de caractere dintr-un nume
sunt luate în considerare, adică două nume sunt diferite dacă ele diferă
în primele 32 de caractere ale lor.
Exemple de nume: a, b1, a1b2c3, Fs, _hG, Nume, nUME, …
Se recomandă ca numele să fie sugestive, adică ele să sugereze
pe cât posibil scopul alegerii lor sau a datei pe care o reprezintă.
3.5. Cuvintele cheie ale limbajului C
În limbajul C există un număr de cuvinte care au o utilizare
predefinită, numite cuvinte cheie. Utilizatorul nu poate să utilizeze
aceste cuvinte pentru a denumi variabile sau funcţii într-un program.
Tabelul următor prezintă cuvintele cheie ale limbajului C:
Cuvintele cheie ale limbajului C
auto default float register struct volatile
break do for return switch while
case double goto short typedef char
else if signed union const enum
int sizeof unsigned continue extern long
static void

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

56
constante simbolice. O constantă simbolică este un nume a cărui
valoare nu poate fi modificată în domeniul său.
În C există trei feluri de constante simbolice:
1. orice valoare de orice tip poate fi folosită ca şi
constantă prin adaugarea cuvântului cheie const la definirea sa;
2. un set de constante întregi definite ca o enumerare;
3. orice nume de vector sau funcţie.
3.6.1. Constante caracter
O constantă caracter este un caracter inclus între apostrofuri. De
exemplu, 'a', 'A' şi '%' sunt constante caracter. Valoarea unei constante
caracter este chiar valoarea numerică corespunzătoare caracterului dat
în setul de caractere al maşinii. De pildă, dacă pe un calculator
caracterele se reprezintă în cod ASCII, atunci constanta '1' are
valoarea 0618, 4910 sau 3116. Constantele caracter pot fi folosite în
operaţii de calcul exact ca şi întregii.
Caracterele pot fi reprezentate şi prin secvenţe escape (de
exemplu, prin constanta '\n' se introduce caracterul newline).
3.6.2. Constante întregi
Constantele întregi se reprezintă în 4 forme: zecimale, octale,
hexazecimale şi constante caracter. Constantele zecimale sunt cel mai
frecvent folosite şi se reprezintă ca şiruri de cifre zecimale. O
constantă care începe cu zero urmat de x (0x) este un număr
hexazecimal, iar o constantă care începe cu zero este un număr octal.
Pentru a reprezenta cifrele hexazecimale 10,...,15 se folosesc literele
a,...,f sau literele mari corespunzătoare. Notaţiile octale şi
hexazecimale sunt utile în exprimarea succesiunilor de biţi.
Exemplu:
int hex = 0xFF; /* numărul 255 în zecimal */
int oct = 011 ; /* numărul 9 în zecimal */

3.6.3. Constante în virgulă mobilă


O constantă în virgulă mobilă are tipul float, double sau long
double. Compilatorul, ca şi în cazul constantelor întregi, trebuie să
semnaleze eroare în cazul în care constantele sunt prea mari pentru a
putea fi reprezentate.
Exemple de constante în virgulă mobilă:
123.23 .23 0.23 1.0 1. 1.2e10 1.256-15

57
Observaţie. În interiorul constantelor întregi sau reale nu pot
apare spaţii albe. De exemplu, 56.62 e - 17 nu este o constantă în
virgulă mobilă, ci sunt de fapt 4 atomi lexicali: 56.62, e, -, 17 şi se va
genera o eroare de sintaxă.
Dacă se doreşte o constantă de tip float, aceasta se poate defini
astfel: const float pi8 = 3.14159265;
3.6.4. Constante şir
Constantele şir constau din caractere cuprinse între ghilimele,
ca în faimosul “Hello, world\n“. Constantele şir, spre deosebire de
altele, au o locaţie în memoria calculatorului. Caracterele dintr-un şir
sunt stocate în memorie, iar valoarea numerică a constantei este
adresa acestei memorii. În plus, compilatorul stochează caracterul null
‘\0’ la sfârşitul şirului, marcând astfel sfârşitul său.
În cazul setului de caractere ASCII, constanta şir “0“ arată astfel
în memorie:
3000 48 (30H) 0
‘0’ ‘\0’
iar valoarea constantei este adresa sa din memorie (în exemplul de mai
sus valoarea 3000), pe când valoarea caracterului 0 este 48 sau 30H.
Cea mai scurtă constantă şir este şirul null scris drept “ “ şi este
stocat în memorie ca un singur caracter null ‘\0’. De exemplu, dacă
avem constanta şir “ABC“ atunci, la o anumită adresă de memorie
vom avea:
Adresa 61
‘A’
Adresa+1 62
‘B’
Adresa+2 63
‘C’
Adresa+3 0
‘/0’
Valoarea constantei şir “ABC“ va fi Adresa, adică valoarea
adresei locaţiei în care se stochează primul caracter din şir.
Ca o ultimă remarcă, vom face precizarea că din punctul de
vedere al reprezentării, constanta caracter ‘A’, spre exemplu, este

58
diferită de consta şir “A“, care se stochează în memorie la o anumită
adresă şi se termină cu caracterul null, deci are alocaţi doi octeţi.
Fiecare constantă şir conţine cu un caracter mai mult decât
numărul de caractere din şir deoarece aceasta se termină totdeauna cu
caracterul \0 care are valoarea 0.
De exemplu, sizeof("asaf") va fi 5.
Tipul unui şir este vector de un număr de caractere a.i. "asaf"
are tipul char[5]. Şirul vid se notează prin " " şi are tipul char[1]. De
notat că, pentru fiecare şir s, strlen(s) == sizeof(s) - 1, deoarece funcţia
strlen() nu numără şi terminatorul \0.
În interiorul unui şir se poate folosi convenţia de notaţie cu \.
Aceasta face posibilă reprezentarea caracterului ghilimele (") şi \ în
interiorul unui şir. Cel mai frecvent caracter folosit este caracterul
'\n'=newline (NL).
De exemplu, instrucţiunea:
printf ("beep at end of message\007\n");
determină scrierea unui mesaj, a caracterului BEL şi a caracterului
NL. O secvenţă de forma \n într-un şir nu determină introducerea unui
caracter NL în şir, ci este o simplă notaţie (\n este caracter neafişabil).
Nu este permisă continuarea şirurilor de caractere de pe o linie
pe alta.
Atunci când se include o constantă numerică într-un şir de
caractere utilizând notaţia octală sau hexazecimală este recomandat să
se folosească 3 cifre pentru număr.
Exemplu:
char v1[] = "a\x0fah\0129";//'a' 'x0f' 'a' 'h' '\012' '9'
char v2[] = "a\xfah\ 129"; /* 'a' 'xfa' 'h' '\12' '9' */
char v3[] = "a\xfad\127"; /* 'a' 'xfa' 'd' '\127' */

3.6.5. Constanta zero


Zero poate fi utilizat ca o constantă pentru tipurile întregi, în
virgulă mobilă sau pointer. Nu se recomandă alocarea unui obiect la
adresa zero. Tipul lui zero va fi determinat de context.
3.6.6. Obiecte constante
Cuvântul cheie const poate fi inclus într-o declaraţie a unui
obiect pentru a determina ca tipul acestui obiect să fie constant şi nu
variabil.
Exemplu : const int model = 145;
const int v[ ] = {1, 2, 3, 4};

59
Deoarece nu i se poate atribui o valoare, o constantă poate
fi doar iniţializată. Declarând ceva ca fiind constant, ne asigurăm că
valoarea sa nu se modifică în domeniul său. Astfel instrucţiunile:
model = 165; /* eroare */
model++; /* eroare */
vor determina apariţia unor mesaje de eroare corespunzătoare.
De notat că const modifică un tip ceea ce înseamnă că
restricţionează felul în care se poate utiliza un obiect, şi nu modul de
alocare. Pentru o constantă, compilatorul nu rezervă memorie
deoarece i se cunoaşte valoarea (precizată la iniţializare). Mai mult,
iniţializatorul pentru o expresie constantă este, de obicei (dar nu
întotdeauna), o expresie constantă. Dacă este aşa, aceasta poate fi
evaluată în timpul compilării.
3.6.7. Enumerări
Folosirea cuvântului cheie enum este o metodă alternativă
pentru definirea constantelor întregi, ceea ce este uneori mult mai util
decât utilizarea lui const. De exemplu,
enum {ASM , AUTO , BREAK };
defineşte 3 constante întregi denumite enumeratori şi le atribuie valori.
Deoarece valorile enumeratorilor sunt atribuite implicit,
începând cu 0, aceasta este echivalentă cu:
const ASM = 0;
const AUTO = 1;
const BREAK = 2;
O enumerare poate avea nume. De exemplu,
enum Keyword {ASM , AUTO , BREAK };
defineşte o enumerare cu numele Keyword. Numele enumerării devine
sinonim cu int şi nu cu un nou tip. Declararea unei variabile Keyword
în loc de int poate oferi atât utilizatorului, cât şi compilatorului, o
sugestie asupra modului de utilizare. De exemplu,
enum Keyword Key; //declara var. Key de tip enum Keyword
switch (Key) {
case ASM:
...........
break;
case BREAK:
...........
break; }
determină compilatorul să iniţieze un avertisment deoarece sunt
folosite numai două din cele trei valori ale lui Key.

60
Capitolul IV

OPERANZI ŞI OPERATORI ÎN C

4.1. Operanzi

O expresie, în limbajul C, este formată dintr-un operand sau mai


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

Exemple:
1. 6353 – este o constantă întreagă zecimală de tip int şi
reprezintă un operand constant de tip int.
2. float x2 – reprezintă declaraţia variabilei x2, iar
numele x2 reprezintă un operand de tipul float.
3. 0xa13d – este o constantă întreagă hexazecimală de
tip unsigned şi reprezintă un operand de tipul unsigned.
4. produs(a,b) – este un apel al funcţiei produs. Această
funcţie reprezintă un operand al cărui tip coincide cu tipul valori
returnate de funcţia produs.
4.2. Operatori
Operatorii pot fi unari sau binari în funcţie de numărul de
operanzi cărora li se aplică. Un operator unar se aplică unui singur
operand, iar un operator binar se aplică la doi operanzi. Operatorul

61
binar se aplică la operandul care îl precede imediat şi la care îl
urmează imediat.
Operatorii limbajului C nu pot avea ca operanzi constante şir
(şiruri de caractere). C are mai multe clase generale de operatori:
aritmetici, relaţionali şi logici, operatori pentru prelucrare biţi, precum
şi câţiva operatori speciali pentru sarcini particulare.
La scrierea unei expresii se pot utiliza operatori din toate
clasele. La evaluarea unei astfel de expresii este necesar să se ţină
seama de priorităţile operatorilor care aparţin diferitelor clase de
operatori, de asociativitatea operatorilor de 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;
62
- expresia a=b=c=d-15 este echivalentă cu a=(b=(c=(d
-15)));
- expresia a%-b*c este echivalentă cu (a%(-b))*c;
4.2.2. Operatori de incrementare şi decrementare
În C, operaţiile de forma i = i+1 şi j = j-1 pot fi programate
folosind doi operatori unari specifici şi anume ++ pentru incrementare
cu 1 şi -- pentru decrementare cu 1. Aceşti operatori pot fi folosiţi atât
ca prefix pentru variabile (de exemplu, ++i, --j) sau ca sufix (i++, j--).
Între aceste moduri de utilizare există diferenţe. Astfel, în expresia
++i, variabila i este incrementată înainte de a-i folosi valoarea, în timp
ce în expresia i++, variabila i este incrementată după întrebuinţarea
valorii acesteia.
Exemplu: Considerăm secvenţa:
x = 10;
y = ++x;
Dacă se afişează y, atunci vom găsi y = 11 deoarece mai întâi se
incrementează x şi apoi se atribuie valoarea lui y.
Dacă scriem: x = 10;
y = x++;
vom găsi y=10 (mai întâi se face atribuirea lui x la y şi apoi
incrementarea lui x).
Precedenţa tuturor operatorilor aritmetici este:
Înaltă ++ --
+ - (unari)
* / %
Scăzută + - (binari)
Operatorii de aceeaşi precedenţă sunt evaluaţi de al stânga la
dreapta.
4.2.3. Operatori relaţionali
Operatorii relaţionali permit compararea a două valori şi luarea
unei decizii după cum rezultatul comparării este adevărat sau fals.
Dacă rezultatul operaţiei este fals, atunci valoarea returnată este zero,
iar dacă este adevărat, valoarea returnată este 1.
Operatorii relaţionali folosiţi în C sunt:

63
== egal
!= diferit
< mai mic strict
<= mai mic sau egal
> mai mare strict
>= mai mare sau egal
Operatorii relaţionali au o precedenţă mai mică 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 ()
64
{ 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 11 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’

65
Deoarece limbajul C a fost gândit să înlocuiască limbajul de
asamblare în majoritatea operaţiilor de programare, acesta trebuie să
aibă capacitatea să suporte toţi (sau cel puţin mulţi) operatorii utilizaţi
în asamblare.
Operatorii pentru prelucrarea biţilor se aplică biţilor dintr-un
byte sau cuvânt, ambele variabile de tip char şi short int. Aceşti
operatori nu se aplică tipurilor float, double, long double, void sau
altor tipuri mai complexe.
Operatorii pentru prelucrarea biţilor utilizaţi în C sunt:
& AND
| OR
^ exclusive OR (XOR)
~ complement faţă de unu (NOT)
>> deplasare dreapta
<< deplasare stânga
Operaţiile pe biţi sunt utilizate de obicei în drivere, pentru
testarea şi mascarea anumitor biţi. De exemplu, operaţia AND poate fi
folosită pentru ştergerea unor biţi dintr-un byte sau dintr-un cuvânt,
OR poate fi folosită pentru setarea unor biţi, iar XOR pentru
complementarea unor biţi şi testarea egalităţii a 2 bytes.
Observaţie: Operatorii relaţionali şi logici (&&, ||, !,...) produc
totdeauna un rezultat care este fie 0, fie 1, pe când operatorii similari
destinaţi prelucrării biţilor pot produce orice valoare arbitrară, în
concordanţă cu operaţia specifică.
Operatorii >> şi << deplasează toţi biţii dintr-o variabilă la
dreapta, respectiv la stânga. Forma generală a operaţiilor de deplasare
este:
variabilă >> număr_de_poziţii_bit - pentru deplasare dreapta.
variabilă << număr_de_poziţii_bit - pentru deplasare stânga.
În poziţiile rămase libere, după deplasare, se introduc zerouri.
Operaţiile de deplasare pot fi utile când se decodifică perifericele de
intrare cum ar fi convertoarele D/A (digital/analogice) şi când se
citesc informaţii de stare. Operatorii de deplasare se pot utiliza şi
pentru realizarea cu rapiditate a operaţiilor de înmulţire şi împărţire.
Se ştie că o deplasare stânga cu 1 bit realizează înmulţirea cu 2, iar o
deplasare dreapta cu 1 bit realizează o împărţire cu 2.
Exemplu:
x = 7; 0 0 0 0 0 1 1 1 7

66
x << 1; 0 0 0 0 1 1 1 0 14
x << 3; 0 1 1 1 0 0 0 0 112
x << 2; 1 1 0 0 0 0 0 0 192
x >> 1; 0 1 1 0 0 0 0 0 96
x >> 2; 0 0 0 1 1 0 0 0 24
Următorul exemplu evidenţiază efectul operatorilor de deplasare:
# include <stdio.h>
void disp_binary();
/* prototipul functiei disp_binary() */
void main() {
int i = 1, t;
for (t=0;t<8;t++) {
disp_binary(i);
i=i<<1;}
printf (" \n");
for (t=0;t<8;t++) {
i=i>>1;
disp_binary(i);}}

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

1 0 1 0 1 0 1 0

0 1 0 1 0 1 0 1

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

68
Acest program realizează rotirea numărului 14710=100100112
cu 6 poziţii
10010011
00100111
01001110
10011100
00111001
01110010
11100100
Programul de mai sus funcţionează pentru numere
reprezentabile pe un octet (mai mici de 255). Dacă dorim să facem o
rotire pe doi octeţi, atunci se poate modifica programul de mai sus
după cum urmează:
# include <stdio.h>
union rotate {
char ch[3];
unsigned int i;
} rot;
void disp_binary();
void rotate_bit();
void main() {
register int t;
rot.i=17843;
for (t=0;t<7;t++) {
disp_binary(rot.i);
rotate_bit(&rot);}}
/* se defineste functia rotate_bit() */
void rotate_bit(union rotate *rot)
{rot->ch[2]=0;
rot->i=rot->i<<1;
if (rot->ch[2]) rot->i=rot->i|1;}
/* se defineste functia disp_binary() */
void disp_binary(int i)
{register int t;
for (t=32768;t>0;t=t/2)
if (i&t) printf("1");
else printf("0");
printf("\n");}
Operatorul " ~ " realizează complementul faţă de 1. O utilizare
interesantă a complementului faţă de 1 este aceea că ne permite să
vedem setul caracterelor extinse implementate în calculator:
# include <stdio.h>
# include <conio.h>
void main() {char ch;
do {ch = getch();
printf ("%c %c\n", ch, ~ch);} while (ch != 'q');}

69
4.2.6. Operatorul de atribuire
În C, operatorul de atribuire (asignare) este semnul egal (=).
Valoarea expresiei din dreapta se atribuie variabilei din stânga
operatorului "=". În C, forma:
suma = a + b + c;
trebuie privită ca o nouă expresie, numită expresie de asignare.
Valoarea ei este chiar valoarea expresiei din dreapta operatorului de
atribuire. Dacă într-o expresie se fac mai multe atribuiri, atunci
evaluarea se face de la dreapta la stânga:
x = y = z = 0 este echivalentă cu (x=(y=(z=0)));
O expresie de atribuire de forma x = x + 5 în care variabila
din stânga apare imediat după operatorul = se poate scrie într-o formă
compactă de tipul x += 5, unde operatorul += este tot un operator de
atribuire. Majorităţii operatorilor binari le corespund operatori de
atribuire de forma "op = " unde op poate fi : +, -, *, %, <<, >>, &, ^ ,
Operatorii de atribuire (asignare) sunt:
= , += , -= , *= , /= , %= , <<= , >>= , &= , ^= , |=
Deci, o expresie de asignare de forma :
var = (var) op (expr)
unde var este o variabilă şi expr este o expresie, admite o reprezentare
compactă de forma:
var op= expresie
Într-o formă compactă, ca mai sus, var este evaluată o singură dată.
4.2.7. Operatorul sizeof
Operatorul sizeof returnează numărul de octeţi necesar
memorării variabilei sau tipului datei care este operandul său. Dacă
sizeof operează asupra unui tip de date, atunci tipul trebuie să apară
între paranteze. De exemplu, sizeof(char) va fi 1, sizeof(int)
va fi 4 etc., deci rezultatul este un număr întreg, fără semn. Fişierul
standard stddef.h defineşte tipul size_t al rezultatului oferit de
operatorul sizeof. Dacă sizeof se aplică unui tablou, rezultatul este
numărul total de octeţi din tablou. Exemplul din programul următor
prezintă dimensiunea principalelor tipuri de date:
# include <stdio.h>
# include <stddef.h>

70
void main(){
printf("\nTip caracter pe %d octet",sizeof(char));
printf("\nTip short int pe %d octeti",sizeof(short
int));
printf("\nTip int pe %d octeti",sizeof(int));
printf("\nTip long int pe %d octeti",sizeof(long int));
printf("\nTip float pe %d octeti",sizeof(float));
printf("\nTip double pe %d octeti",sizeof(double));
printf("\nTip long double pe %d octeti\n", sizeof(long
double));}
În urma execuţiei acestui program, se va afişa (rezultatele
depind de tipul de procesor sau de compilator):
Tip caracter pe 1 octet
Tip short int pe 2 octeti
Tip int pe 4 octeti
Tip long int pe 4 octeti
Tip float pe 4 octeti
Tip double pe 8 octeti
Tip long double pe 8 octeti

4.2.8. Operatorul ternar ?


Operatorul " ? " poate fi utilizat pentru a înlocui instrucţiunea if
/ else având forma:
if (conditie)
expresie1
else
expresie2
Operatorul ternar " ? " necesită trei operanzi şi are forma
generală:
Expr1 ? Expr2 : Expr3
unde Expr1, Expr2 şi Expr3 sunt expresii.
Se evaluează expresia Expr1. Dacă este adevărată, se evaluează
Expr2, care devine valoarea întregii expresii. Dacă Expr1 este falsă, se
evaluează Expr3, iar valoarea acesteia devine valoarea întregii
expresii:
Exemplu:
x = 10;
y = x > 9 ? 100 : 200;
Cum 10 > 9, valoarea lui y va fi 100. Dacă x ar fi mai mic decât
9, y va primi valoarea 200.
Acelaşi program scris cu if /else va fi:
x = 10;
if (x > 9) y = 100;
else y = 200;

71
În alcătuirea expresiilor din declaraţia operatorului ternar " ? "
pot fi folosite şi funcţii:
Exemplu:
# include <stdio.h>
f1();
f2(); // prototipurile functiilor f1() si f2()
void main() {
int t;
printf (": ");
scanf("%d",&t); // se introduce numarul intreg t
t?f1()+f2(t): printf(" S-a introdus zero\n");}
f1() {printf ("S-a introdus "); }
f2(int n) {printf ("%d\n", n);}
Dacă se introduce zero, atunci va fi apelată printf() şi va afişa "
S-a introdus zero". Dacă se introduce alt număr, atunci programul va
executa atât funcţia f1(), cât şi funcţia f2().
4.2.9. Operatorul virgulă
Operatorul virgulă se utilizează într-un şir în care se introduc
mai multe expresii. Astfel, instrucţiunea:
x = (y = 3, y+1),
are că efect atribuirea valorii 4 variabilei x.
Deci expresiile separate prin virgulă sunt evaluate de la stânga
la dreapta, prima expresie evaluată căpătând valoarea void. Dacă se
utilizează un operator de atribuire, valoarea atribuită variabilei din
stânga operatorului de atribuire este valoarea ultimei expresii din
dreapta, după evaluare. Exemplu:
y = 10;
x = (y = y - 5, 30 / y);
Variabila x va căpăta valoarea 6.
Observaţie Deoarece operatorul virgulă are o precedenţă mai
mică decât operatorul de atribuire, pentru ca atribuirile să se facă
corect, trebuie utilizate paranteze.
4.2.10. Operatorul de forţare a tipului sau de conversie
explicită (expresie cast)
Adesea se doreşte specificarea conversiei valorii unui operand
spre un tip dat. Acest lucru este posibil folosind o construcţie de
forma: (tip) operand
Printr-o astfel de construcţie valoarea operandului se
converteşte spre tipul indicat în paranteze. În construcţia de mai sus
(tip) se consideră că este un operator unar. Acest operator este
72
cunoscut sub numele de operator de forţare a tipului sau de conversie
explicită. De cele mai multe ori însă este utilizată denumirea engleză a
operatorului şi anume expresie cast.
Exemplu:
Presupunem că o funcţie oarecare f are un parametru de tip
double. Pentru ca această funcţie să poată fi apelată cu un parametru
int n (n este un parametru de tip întreg) acesta trebuie mai întâi
convertit la tipul double. Acest lucru se poate realiza printr-o atribuire:
double x
f(x=n)
Un alt mod mai simplu de conversie a parametrului întreg spre
tipul double este utilizarea unei expresii cast:
f((double)n)
Operatorul de forţare a tipului fiind unar, are aceeaşi prioritate
ca şi ceilalţi operatori unari ai limbajului C.
4.2.11. Operatorii paranteză
Parantezele rotunde se utilizează fie pentru a include o expresie,
fie la apelul funcţiilor. O expresie inclusă în paranteze rotunde
formează un operand. În acest mod se poate impune o altă ordine în
efectuarea operaţiilor, decât cea care rezultă din prioritatea şi
asociativitatea operatorilor.
Operanzii obţinuţi prin includerea unei expresii între paranteze
impun anumite limite asupra operatorilor. De exemplu, la un astfel de
operand nu se pot aplica operanzii de incrementare şi decrementare
sau operatorul adresă. Astfel construcţiile:
(a-5+b)++ --(a+b) &(a*b)
sunt eronate.
La apelul unei funcţii, lista parametrilor efectivi se include între
paranteze rotunde. În acest caz se obişnuieşte să se spună că
parantezele rotunde sunt operatori de apel de funcţie.
Parantezele pătrate include expresii care reprezintă indici. Ele se
numesc operatori de indexare.
Parantezele sunt operatori de prioritate maximă. Operatorii
unari au prioritatea imediat mai mică decât parantezele.
4.2.12. Operatorul adresă
Operatorul adresă este unar şi se notează prin caracterul
&. El se aplică pentru a determina adresa de început a zonei de

73
memorie alocată unei date. În forma cea mai simplă, acest
operator se utilizează în construcţii de forma: &nume
unde nume este numele unei variabile simple sau al unei structuri. În
cazul în care nume este numele unui tablou, atunci acesta are ca
valoare chiar adresa de început a zonei de memorie alocată tabloului
respectiv şi, în acest caz, nu se mai utilizează operatorul adresă &.
4.2.13. Alţi operatori ai limbajului C
În limbajul C se mai utilizeză şi operatorii: „ * ” , „ . ” şi „->”
Operatorul „ * ” unar (a nu se confunda cu operatorul aritmetic
binar de înmulţire) se utilizează pentru a face acces la conţinutul unei
zone de memorie definită prin adresa ei de început. Se obişnuieşte să
se spună că operatorul de adresă & este operator de referenţiere, iar
operatorul „ * ” este operator de dereferenţiere.
Operatorii „ . ” şi „ -> ” se utilizează pentru a se accesa
componentele unei structuri. Ei au prioritate maximă, având aceeaşi
prioritate cu parantezele.
4.2.14. Regula conversiilor implicite şi precedenţa operatorilor
Regula conversiilor implicite se aplică la evaluarea expresiilor.
Ea acţionează atunci când un operator binar se aplică la doi operanzi
de tipuri diferite. În acest caz, operandul de tip inferior se converteşte
spre tipul superior al celuilalt operand şi rezultatul este de tip superior.
Înainte de toate se convertesc operanzii de tip char şi enum în tipul int.
Dacă operatorul curent se aplică la operanzi de acelaşi tip,
atunci se execută operatorul respectiv, iar tipul rezultatului coincide cu
tipul comun al operanzilor. Dacă rezultatul aplicării operatorului
reprezintă o valoare în afara limitelor tipului respectiv, atunci
rezultatul este eronat (are loc o „depăşire”).
Exemplu: Rezultatul împărţiirii 7/3 este 2 şi nu 2.5 deoarece
cei doi operanzi sunt de tip întreg şi prin urmare rezultatul (care este
de tip real) este şi el convertit la tipul întreg.
Dacă operatorul binar se aplică la operanzi de tipuri diferite,
atunci se face o conversie înainte de execuţia operatorului, conform
algoritmului umător:
1. Dacă unul din operanzi este de tip long double, atunci celălalt
operand se converteşte spre tipul long double iar tipul rezultatului
aplicării operatorului este de asemenea de tip long double.

74
2. Altfel, dacă unul din operanzi este de tip double atunci celălalt
operand se converteşte spre tipul double iar tipul rezultatului aplicării
operatorului este de asemenea de tip double.
3. Altfel, dacă unul din operanzi este de tip float atunci celălalt
operand se converteşte spre tipul float iar tipul rezultatului aplicării
operatorului este de asemenea de tip float.
4. Altfel, dacă unul din operanzi este de tip unsigned long atunci
celălalt operand se converteşte spre tipul unsigned long iar tipul
rezultatului aplicării operatorului este de asemenea de tip unsigned
long.
5. Altfel, dacă unul din operanzi este de tip long atunci celălalt
operand se converteşte spre tipul long iar tipul rezultatului aplicării
operatorului este de asemenea de tip long.
6. Altfel, unul din operanzi trebuie sa fie de tip unsigned, celălalt
de tip int şi acesta se converteşte spre tipul unsigned, iar tipul
rezultatului aplicării operatorului este de tip unsigned.
Precedenţele operatorilor C sunt prezentate în tabelul următor.
Operatorii aflaţi pe aceeaşi linie au aceeaşi prioritate. Ei se asociază de
la stânga la dreapta, exceptând operatorii unari, condiţionali şi de
atribuire, care se asociază de la dreapta la stânga.
Precedenţa Operatorul
Înaltă () [ ] -> .
! ~ ++ -- - (type) * & sizeof
* / %
+ -
<< >>
< <= > >=
== !=
&
^
|
&&
||
?:
Scăzută = += -= *= /=
,

75
Capitolul V

INSTRUCŢIUNI

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


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

Eticheta formată dintr-un identificator defineşte identificatorul


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

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


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

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

5.2. Instrucţiuni expresie


Cele mai multe instrucţiuni sunt instrucţiunile expresie, care au
forma:
[ expresie ];
unde expresie este opţională.
Majoritatea instrucţiunilor expresie sunt atribuiri sau apeluri de
funcţii. Deci, o instrucţiune expresie constă dintr-o expresie urmată de
caracterul ";".
Exemplu: a = b * c + 3;
printf ("FAC. DE AUTOMATICA");
Dacă expresia expresie de mai sus lipseşte, construcţia “;“ se
numeşte instrucţiune vidă. Aceasta nu are nici un efect, dar este
utilizată pentru a înlocui un corp vid al unei iteraţii sau pentru a plasa
o etichetă.
5.3. Instrucţiuni compuse
O instrucţiune compusă este o posibilă listă de declaraţii şi/sau
instrucţiuni închise între acolade. Exemplu:
{ a = b + 2; b++; }
O instrucţiune compusă se numeşte bloc. Un bloc ne permite să
tratăm mai multe instrucţiuni ca pe una singură. Corpul unei funcţii
este o instrucţiune compusă.
Domeniul de vizibilitate al unui identificator declarat într-un
bloc se întinde din punctul declaraţiei până la sfârşitul blocului.
Identificatorii utilizaţi într-un bloc pot fi ascunşi prin declaraţii de
acelaşi nume în blocurile interioare blocului iniţial.
Exemplu:
# include <stdio.h>
int x = 34; /* x este global */
void main(void) {
int *p = &x; /*p preia adresa variabilei globale*/
int x1, x2;
printf("x = %d\n", x);

77
{int x; /*x este local si il ascunde pe cel global */
x = 1; /* atribuirea se face la cel local */
x1 = x;
printf("x = %d\n", x1);}
{ int x; /* se ascunde prima variabila locala */
x = 2; /* se atribuie valoarea 2 acestui x */
x2 = x;
printf("x = %d\n",x2); }
printf("x = %d %d %d \n",x,x1,x2); }

5.4. Instrucţiuni de selecţie


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

5.4.2. Instrucţiuni de selecţie multiplă: if - else if


Într-o instrucţiune if se poate include, pe o ramură, o altă
instrucţiune if. În acest fel se creează posibilitatea de a codifica
structuri de selecţie multiplă, folosindu-se perechi else if. O asemenea
construcţie este de forma:
if (conditie1)
instructiune1;
else if (conditie2)
instructiune2;
else if (conditie3)
instructiune3;
. . . . . . . . . . . . . . . .
else if (conditieN)
instructiuneN;
else
instructiuneN+1;
În acest caz, condiţiile sunt testate în ordine. Dacă una din ele
este adevărată, atunci este executată instrucţiunea corespunzătoare,
după care controlul este transferat la instrucţiunea următoare din
program. Codul pentru fiecare alternativă poate fi format dintr-o
instrucţiune simplă (inclusiv instrucţiunea vidă) sau dintr-un bloc
delimitat prin { şi }. Dacă nici una dintre expresii nu este adevărată,
atunci se execută secvenţa corespunzătoare ultimei alternative
introdusă prin else. Această ultimă alternativă nu este obligatorie,
structura putându-se încheia după secvenţa notată cu instructiuneN.

79
Exemplu: Considerăm un program care realizează conversiile inch-cm
şi cm-inch. Presupunem că indicăm unitatea intrării cu i pentru inch şi
c pentru centimetru:
# include <stdio.h>
# include <conio.h>
void main(void) {
const float fact = 2.54;
float x,in,cm;
char ch = 0;
printf ("\nIntroduceti numarul: \n");
scanf("%f",&x);
printf("\nIntroduceti unitatea: \n");
ch = getche(); /* se introduce un caracter de la
tastatura care se afiseaza pe ecran */
if (ch == 'i') {
in = x;
cm = x * fact;}
else if(ch == 'c') {
in = x/fact;
cm = x; }
else in = cm = 0;
printf("\n%5.2f in = %5.2f cm \n",in,cm); }

5.4.3. Instrucţiunea switch


Într-o instrucţiune de selecţie switch, se compară, pe rând, o
valoare cu constantele dintr-o mulţime şi în momentul găsirii unei
coincidenţe se execută instrucţiunea sau blocul de instrucţiuni asociate
acelei constante. Forma generală a instrucţiunii switch este:
switch (variabila) {
case constanta1 :
secventa_instructiuni_1
break;
case constanta2 :
secventa_instructiuni_2
break;
case constanta3 :
secventa_instructiuni_3
break;
. . . . . . . . . . . . . . . . . . .
case constantaN :
secventa_instructiuni_N
break;
default :
secventa_instructiuni_N+1
}

80
Instrucţiunea switch realizează transferul controlului la una din
secvenţele de instrucţiuni dacă valoarea variabila ce trebuie să aibă
tipul întreg coincide cu una din constantele de dupa case. Secvenţa de
instrucţiuni se execută pâna se întâlneşte break, după care se trece la
instrucţiunea imediat următoare după switch. Dacă nu se găseşte nici o
coincidenţă, se execută secvenţa de instrucţiuni de după default, iar
dacă default lipseşte, deoarece prezenţa acesteia este opţională, se
trece la instrucţiunea următoare.
Exemplu: Decizia din exemplul anterior poate fi realizată şi astfel:
# include <stdio.h>
# include <conio.h>
void main(void) {
const float fact = 2.54;
float x, in, cm;
char ch = 0;
printf ("\nIntroduceti numarul: \n");
scanf("%f", &x);
printf("\nIntroduceti unitatea: \n");
ch = getche();
switch(ch) {
case 'i': in = x;
cm = x * fact;
break;
case 'c': in = x/fact;
cm = x;
break;
default: in = cm = 0;
break; }
printf("\n%5.2f in = %5.2f cm \n",in,cm);
}
Observaţie: Constantele case trebuie sa fie distincte.
Pentru a ieşi din instrucţiunea switch se foloseşte instrucţiunea break.
Exemplu:
# include <stdio.h>
void main (void) {
int t;
for (t = 0; t < 10; t++)
switch (t) {
case 1 :
printf ("Now");
break;
case 2 :
printf (" is ");
break;
case 3 :
81
case 4 :
printf (" the ");
printf (" time for all good men \n");
break;
case 5 :
case 6 :
printf (" to ");
break;
case 7 :
case 8 :
case 9 :
printf (" . ");
break; } }
Rulând acest program, vom obţine:
Now is the time for all good men
the time for all good men
to to . . .
Instrucţiunea switch este foarte eficientă în scrierea
programelor care afişează pe ecran o listă de opţiuni (un meniu) din
care utilizatorul alege câte una şi o execută. Instrucţiunile switch pot
fi şi incluse (încuibate) una în alta.

5.5. Instrucţiuni repetitive


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

82
Nu întotdeauna ciclul for trebuie să se desfăşoare în sensul
creşterii variabilei de control. Putem crea cicluri for în care variabila
de control se decrementează.
Exemplu: Programul următor afişează numerele de la 100 la 1.
# include <stdio.h>
void main (void) {
int x;
for (x =100; x > 0; x--) printf("%d", x); }
Nu există restricţii în incrementarea sau decrementarea
variabilei de control a ciclului.
Exemplu: Următorul program afişează pe ecran numerele de la 0 la
100 din 5 în 5:
# include <stdio.h>
void main (void) {
int x;
for (x = 0; x <= 100; x = x + 5) printf ("%d", x); }
Instructiunea instrucţiune din declaraţia ciclului for poate fi o
instrucţiune simplă sau un bloc (un grup de instrucţiuni delimitate de
acolade) care va fi executat repetitiv.
Exemplu: Programul următor afişează pe ecran numerele de la 0 la
99, precum şi pătratul acestora:
# include <stdio.h>
void main (void) {
int i;
for (i = 0; i < 100; i++) {
printf (" Acesta este i : %3d", i);
printf (" si i patrat : %5d \n", i*i); } }
Exemplu: Calculul factorialului unui număr: n! = 123...n
# include <stdio.h>
void main (void)
{ int n, i;
long int factorial;
printf ("Introduceti n : ");
scanf ("%d", &n);
factorial = 1;
for (i = 1; i <= n; i++)
factorial *= i;
printf (" %d ! = %ld \n", n, factorial); }
Instrucţiunile for pot fi incluse una în alta.
Exemplu: Programul următor parcurge un şir de caractere de la stânga
la dreapta, afişând subşirurile ce au ca bază primul caracter.
# include <stdio.h>
# include <string.h>
void main (void)

83
{ int l, n, i;
char sir[81];
puts ("Tastati un sir terminat cu <CR> : ");
gets (sir);
l = strlen (sir);
for (n = 0; n <= l; ++n) {
for (i = 0; i < n; ++i)
putchar (sir[i]);
putchar ('\n'); } }
Variante ale ciclului for: Limbajul C permite mai multe
variante ale ciclului for care determină creşterea flexibilităţii acestuia
în diferite situaţii. Una din cele mai utilizate variante constă în
folosirea a mai multe variabile de control a ciclului. În exemplul
următor, atât x cât şi y sunt variabile de control a ciclului:
# include <stdio.h>
void main (void)
{ int x, y;
for (x = 0, y = 0; x + y < l00; x++, y++)
printf ("%d ", x + y);
}
Acest program tipăreşte numerele de la 0 la 98 din 2 în 2. Se observă
că iniţializările şi incrementările celor două variabile sunt separate
prin virgulă.
Terminarea ciclului presupune testarea nu numai a variabilei de
control cu anumite valori prestabilite, ci condiţia de terminare a
ciclului poate fi orice expresie C corectă.
Exemplu: Considerăm un program pentru antrenarea unui copil în
exerciţiile de adunare. Dacă copilul vrea să se oprească se apesa tasta
T, atunci când calculatorul îl întreabă dacă să continue.
# include <stdio.h>
# include <conio.h>
void main (void)
{ int i, j, raspuns;
char terminare = ' ';
for (i=1; i<100; i++) {
for (j=1; j<100 && terminare !='t';j++) {
printf ("\nCit este %d + %d ? ",i,j);
scanf("%d",&raspuns);
if (raspuns != i+j) printf("\nGresit !");
else printf("\nCorect !");
printf(" Continuam ? ");
terminare = getchar(); }
/* Pentru terminare se apasa tasta t */ } }

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

Ieşirea dintr-o bucla for: Pentru terminarea unei bucle for,


chiar şi a buclei for(; ;) se foloseşte instrucţiunea break care se
plasează oriunde în corpul ciclului şi determină încheierea imediată a
ciclului (la întâlnirea acesteia), programul continuându-se cu
instrucţiunea ce urmează după instrucţiunea for.
Exemplu: Acest program va rula până când de la tastatura se apasă
tasta A:
# include <stdio.h>
# include <ctype.h>
void main (void)
{ for (; ;) { ch = getche ();
if (ch == 'a') break; }
printf (" Ai apasat tasta A "); }

85
Utilizarea ciclurilor for fără corp (instrucţiune) : Pentru
crearea unor întârzieri de timp se pot folosi cicluri for cu corp vid de
forma:
for (t = 0; t < O_ANUMITA_VALOARE; t++);
Observaţie: De obicei, instrucţiunea for este legată de parcurgerea
unor structuri de date de tip tablou.
5.5.2. Instrucţiunea while
Forma generală a instrucţiunii repetitive while este:
while (conditie)
instructiune;
unde instructiune poate fi o instrucţiune vidă, o instrucţiune simplă
sau un bloc de instrucţiuni ce vor fi executate repetitiv. În timpul
execuţiei se evaluează mai întâi condiţia buclei a cărei valoare trebuie
să fie întreagă. Dacă valoarea calculată este diferită de 0 (condiţie
adevărată), atunci instructiune se execută. Dacă, după o evaluare
(inclusiv prima) rezultă o valoare 0 (condiţie falsă), atunci controlul
este transferat la instrucţiunea ce urmează după while. Astfel,
instrucţiunea asociată cu while se execută repetat, cât timp valoarea
asociată condiţiei este diferită de 0 sau condiţia este adevărată.
Exemplu: Programul următor calculează c.m.m.d.c. pentru o pereche
x, y de numere întregi pozitive.
# include <stdio.h>
void main (void) {
int xi, yi, x, y;
printf (" Introduceti doua numere pozitive: \n");
scanf ("%d %d", &xi, &yi);
x = xi; y = yi;
while (x != y)
if (x > y) x -= y;
else y -= x;
printf (" C.m.m.d.c. (%d, %d) = %d", xi, yi, x); }
Metoda de calcul se bazează pe faptul că:
♦ daca x > y, atunci cmmdc (x, y) = cmmdc (x-y, x);
♦ daca x < y, atunci cmmdc (x, y) = cmmdc (x, y-x);
♦ daca x = y, atunci cmmdc (x, y) = x =y .
De exemplu, cmmdc (14, 21) = 7.
Deoarece instrucţiunea while realizează testarea condiţiei la
începutul instrucţiunii, aceasta instrucţiune este bună de utilizat în
situaţiile în care nu se doreşte execuţia buclei, evident dacă condiţia
nu este adevărată.
86
Exemplu: Programul următor realizează centrarea unui text pe ecran:
# include <stdio.h>
# include <ctype.h>
void main (void) {
char sir[255];
printf(" Introduceti un sir de caractere: \n");
gets (sir);
centreaza (strlen (sir));
printf (sir); }
/* Se calculează numărul de spaţii pentru centrarea
unui şir de
caractere cu lungimea lung */
centreaza (lung)
int lung;
{ lung = (80 - lung)/2;
while (lung > 0) {
printf (" "); lung--; } }
Dacă dorim să programăm un ciclu infinit, atunci se poate găsi
o expresie care ramâne tot timpul adevărată. Un exemplu uzual este
următorul:
while (1) { Corpul ciclului }
Ieşirea din ciclu, în acest caz, se asigură prin mecanisme de tip break,
goto sau return.
Corpul ciclului while poate conţine şi numai instrucţiunea vidă.
De exemplu,
while ((ch = getche ()) != 'A');
este o buclă simplă care se execută până când de la tastatură se va
introduce caracterul "A".
Observaţie: Instrucţiunea while reprezintă mecanismul sintactic de
bază pentru a programa cicluri în C.
Reamintim că instrucţiunea for se foloseşte după următorul
format general:
for (initializare; conditie; incrementare) instructiune;
care este echivalentă semantic cu secvenţa:
initializare;
while (conditie) {
instructiune;
incrementare; }
5.5.3. Instrucţiunea do-while
Spre deosebire de ciclurile programate cu while sau for, unde
condiţia de ciclare este verificată la început, în cazul folosisii
mecanismului do-while, condiţia se evaluează după execuţia secvenţei
87
de instrucţiuni ce reprezintă corpul ciclului. Forma generală a buclei
do-while este:
do { instructiune;
} while (conditie);
Semantic, do-while este echivalentă cu secvenţa:
instructiune;
while (conditie)
instructiune;
Deşi acoladele nu sunt necesare când instructiune este o
instrucţiune simplă, de obicei se utilizează pentru a evita confuzia cu
while. Se remarcă faptul că instructiune ce reprezintă corpul ciclului
(adică, o instrucţiune simplă, o instrucţiune compusă sau o
instrucţiune vidă) este executată cel puţin odată. Celelalte execuţii
sunt condiţionate de valoarea întreagă rezultată din evaluarea
condiţiei. Dacă această valoare este 0 (condiţie falsă), atunci controlul
se transferă la următoarea instrucţiune din program; în caz contrar se
execută corpul ciclului şi se reevaluează condiţia.
Exemplu: Următoarea secvenţă asigură preluarea corectă a unei valori
întregi între 1 şi 10:
# include <stdio.h>
void main (void) {
int num;
do {
printf("\n\nIntrod. un intreg între 1 si 10: ");
scanf ("%d", &num);
printf (" Numarul introdus este : %d ", num);
} while (num < 1 || num > 10); }
Un caz tipic de utilizare a instrucţiunii do-while este oferit de
programele interactive în care selecţia unei opţiuni se face pe baza
unui meniu afişat pe ecranul terminalului.
Exemplu: Următorul program implementează o versiune a unui meniu
de verificare a corectitudinii ortografice într-un text:
# include <stdio.h>
# include <ctype.h>
void main (void) {
char ch;
printf ("1. Verificarea ortografiei \n ");
printf ("2. Corectarea erorilor de ortografie \n");
printf ("3. Afisarea erorilor de ortografie \n ");
do {
printf ("\n Introduceti optiunea dumneavoastra: ");
ch=getche(); // Se citeste optiunea de la tastatura
switch (ch) {
case '1':

88
verifica_ortografia();
break;
case '2':
corecteaza_erorile();
break;
case '3':
afiseaza_erorile();
break; }
} while (ch != '1' && ch != '2' && ch != '3'); }
După afişarea opţiunilor, programul va bucla până când se va
selecta o opţiune validă.
Exemplu: Adunarea elementelor a doi vectori:
int a[10], b[10], c[10];
. . . . . . . . . . . . . .
i = 0;
do { c[i] = a[i] + b[i];
i = i + 1;
} while (i < 10);
sau
i = 0;
do { c[i] = a[i] + b[i]; i++;
} while (i < 10);

5.5.4. Bucle încuibate


Când o buclă este introdusă în altă buclă, bucla interioară se
spune a fi inclusă (nested, încuibată) în bucla exterioară.
Exemplu: Programul următor afişează primele 4 puteri ale numerelor
cuprinse între 1 şi 9:
# include <stdio.h>
void main (void)
{ int i, j, k, p;
printf (" i i^2 i^3 i^4 \n ");
for (i = 1; i < 10; i++) {
for (j = 1; i < 5; j++) {
p = 1;
for (k = 1; i < j; k++)
p = p * i;
printf (" %9d ", p); }
printf (" \n "); } }
Când se execută acest program se obţin următoarele rezultate:
i i^2 i^3 i^4
1 1 1 1
2 4 8 16
3 9 27 81
. . . . . . . . . . .
9 81 729 6561
89
Alinierea rezultatelor se datoreşte utilizării în printf() a unui format de
afişare corespunzător (%9d) care precizează dimensiunea minimă a
câmpului specificat.
Un alt exemplu, puţin mai complex, este un program de
înmulţire a două matrice. Evident, în acest caz vom avea 3 bucle for
incluse una în cealaltă.
// Program de inmultire a doua matrici
# include <stdio.h>
float a[100][100],b[100][100],c[100][100];
float elem, s;
int la, ca, lb, cb, lc, cc, i, j, k;
void main(void) {
la=101; ca=101;
lb=ca+1; cb=ca;
printf("Program de inmultire a doua matrici\nSe declara
dimensiunile fiecarei matrici\n\n");
/* Introducem pe rand dimensiunile fiecarei matrici.
Verificam sa nu se depaseasca dimensiunile maxime si
verificam posibilitatea inmultirii matricilor */
while (ca!=lb){
printf("Se verifica daca dimensiunile declarate sunt
compatibile pentru inmultire!\n\n");
while ((la>=100)||(ca>=100)) {
printf("Intoduceti dimensiunile primei matrice");
printf("\nNr. linii matrice A = \n");
scanf("%d",&la);
printf("Nr. coloane matrice A = \n");
scanf("%d",&ca); }
while ((lb>=101)||(cb>=101)) {
printf("Intoduceti dimens. celei de-a doua matrice");
printf("\nNr. linii matrice B = \n");
scanf("%d",&lb);
printf("Nr. coloane matrice B = \n");
scanf("%d",&cb); }
if(ca!=lb) {
la=101;ca=101;
lb=ca+1;cb=ca;} }
/* Se introduc matricile */
for(i=0; i<=la-1; i++)
for(j=0; j<=ca-1; j++) {
printf("a(%d,%d) = ", i, j);
scanf("%f",&elem);
a[i][j] = elem; }
for(i=0;i<=lb-1;i++)
for(j=0;j<=cb-1;j++) {
printf("b(%d,%d) = ",i,j);
scanf("%f",&elem);
90
b[i][j]=elem; }
// Se calculeaza fiecare element al matricei produs
for(i=0;i<=la-1;i++)
for(j=0;j<=cb-1;j++)
{ s=0;
for(k=0;k<=ca-1;k++)
s = s+a[i][k]*b[k][j];
c[i][j] = s; }
// Se afisaza matricile
printf("\n\nA = \n");
for(i=0;i<=la-1;i++)
{ printf("\n");
for(j=0;j<=ca-1;j++)
printf("%6.3f ",a[i][j]); }
printf("\n\nB = \n");
for(i=0;i<=lb-1;i++)
{ printf("\n");
for(j=0;j<=cb-1;j++)
printf("%6.3f ",b[i][j]); }
printf("\n\nC = A*B\n");
for(i=0;i<=la-1;i++)
{ printf("\n");
for(j=0;j<=cb-1;j++)
printf("%6.3f ",c[i][j]); }}

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

TIPURI DE DATE STRUCTURATE

În C există două categorii de tipuri de date structurate:


tablourile şi structurile. Un tablou este o colecţie omogenă de valori
de acelaşi tip identificate printr-un indice, iar o structură este o
colecţie neomogenă de valori identificate prin nume simbolice,
denumite selectori.
6.1. Tablouri unidimensionale
Un tablou este o colecţie de variabile de acelaşi tip care sunt
referite printr-un nume comun. În C, un tablou constă din locaţii de
memorie contigue. Adresa cea mai mică corespunde primului element,
iar adresa cea mai mare corespunde ultimului element. Un tablou
poate avea de la una la mai multe dimensiuni. Accesul la un element
specific al tabloului se face utilizând un index. Cel mai utilizat tablou
este tabloul de caractere. Şirurile de caractere pot fi definite prin
conceptele: vector de caractere şi pointer-caracter.
Declararea unui tablou cu o singură dimensiune are
următoarea formă generală:
tip var_nume[size];
Aici, tip, declară tipul de bază al tabloului. Tipul de bază determină
tipul de dată al fiecărui element al tabloului. var_nume este numele
tabloului, iar size este numărul elementelor pe care le va conţine
tabloul. Exemple:
int a[10]; // vectorul a contine 10 intregi
float v[3]; // vectorul v contine 3 reali
În C toate tablourile folosesc pe zero ca index al primului lor
element. Elementele tabloului a[10] sunt a[0],...,a[9].
Exemplu: Programul următor încarcă un tablou de întregi cu numerele
de la 0 la 9:
void main (void) {
int x[10]; // se rezerva 10 elemente intregi
int t;
for (t = 0; t < 10; t++) x[t] = t; }

93
Pentru un tablou unidimensional, dimensiunea totală, în bytes, a
acestuia va fi:
Total bytes = sizeof (tip) * lungimea_tabloului
Observaţie: Limbajul C nu realizează verificarea dimensiunilor unui
tablou: astfel, nu există nimic care să ne oprească să nu trecem peste
sfârşitul tabloului. Dacă se trece peste sfârşitul unui tablou într-o
operaţie de atribuire, atunci se vor atribui valori unor alte variabile sau
chiar se vor distruge părţi din program.
Exemplu: Deşi următorul program este incorect, compilatorul C nu
semnalează nici o eroare:
void main (void) {
int crash[10], i;
for (i = 0; i < 100; i++) crash[i] = i; }
Se observă că bucla se iterează de 100 de ori, deşi vectorul crash
conţine numai 10 elemente. Aceste verificări rămân în sarcina
exclusivă a programatorului.
Tablourile unidimensionale sunt, de fapt, liste de informaţii de
acelaşi tip. De exemplu, prin rularea programului:
char ch[7];
void main (void)
{ int i;
for (i = 0; i < 7; i++) ch[i] = 'A' + i; }
vectorul “ch“ arată astfel:
ch(0) ch(1) ch(2) ch(3) ch(4) ch(5) ch(6)
A B C D E F G

6.1.1. Constante şir


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

94
şir. Cel mai frecvent caracter folosit este caracterul '\n' = new line
(NL). De exemplu, instrucţiunea:
printf("beep at end of message \007 \n ");
determină scrierea unui mesaj, a caracterului BEL şi a caracterului
NL. Nu este permisă continuarea şirurilor de caractere de pe o linie pe
alta.
Exemplu: "this is not a string
but a syntax error".
O secvenţă de forma \n într-un şir nu determină introducerea
unui caracter NL în şir, ci este o simplă notaţie. Este posibil să folosim
caracterul null într-un şir, dar majoritatea programelor nu testează
dacă mai sunt caractere după el.
6.1.2. Iniţializarea vectorilor de caractere
• Citirea unui şir de la tastatură utilizând funcţiile scanf() şi gets().
o Utilizarea funcţiei scanf(). Exemplu:
# include <stdio.h>
void main (void) {
char nume[21], adresa[41];
printf ("\n Nume: ");
scanf ("%s", nume);
printf ("\n Adresa: ");
scanf ("%s", adresa);
printf ("%s\n%s\n", nume, adresa); }
S-au definit variabilele nume şi adresa ca tip şir de caractere de
maximum 20 şi 40 de caractere. Şirul "%s" care apare în apelul
funcţiei scanf() precizează că se vor citi în variabilele nume, respectiv
adresa, valori de tip şir de caractere. În printf() descriptorul "%s" are
rolul de a preciza cum trebuie convertite valorile datelor de afişat (în
cazul de faţă, valorile variabilelor nume şi adresa).
Funcţia scanf() citeşte un şir de caractere din bufferul de intrare
până când întâlneşte un spaţiu, un caracter TAB, sau ajunge la sfârşitul
acestuia. Astfel, dacă se tastează, "ENE ALEXANDRU", atunci în
variabila nume se va memora doar valoarea "ENE". Pentru a obţine
şirul în întregime este recomandat să se transmită numele sub forma:
"ENE_ALEXANDRU".
o Cea mai bună cale de a introduce un şir de la tastatură constă în
utilizarea funcţiei gets() din fişierul antet "stdio.h". Forma generală a
funcţiei gets() este:
gets (nume_vector)

95
Pentru a citi un şir se apelează gets() având ca argument numele
vectorului, fără nici un index. Funcţia gets() returnează vectorul ce va
păstra şirul de caractere introdus de la tastatură. gets() va continua să
citească caractere până la introducerea caracterului CR.
Exemplu: Programul următor afişează şirul de caractere introdus de la
tastatură.
# include <stdio.h>
void main (void) {
char sir[80];
gets (sir); /* citeste un sir de la tastatura */
printf ("%s", sir); }
Se observă că funcţia printf() poate primi ca argument un şir de
caractere. Dacă se introduce un şir mai lung decât dimensiunea
tabloului, vectorul va fi suprascris.
• Iniţializarea vectorilor de caractere utilizând constantele şir
Folosind constantele şir, vectorii de caractere se iniţializează sub
forma:
char nume_vector[size] = "sir_de_caractere"
unde size = numărul caracterelor din şir plus 1. Exemplu:
# include <stdio.h>
void main (void) {
char nume[14] = "ENE ALEXANDRU";
char adresa[24] = "Str. A. I. Cuza, nr.13";
puts (nume);
puts (adresa); }
Vectorul nume va ocupa începând de la adresa nume, 13 + 1 =
14 octeţi, iar cel de-al doilea vector va ocupa începând de la adresa
adresa, 23 + 1 = 24 locaţii (bytes).
Funcţia puts() scrie pe stdout şirul memorat în vectorul al cărui
nume apare ca parametru al funcţiei puts(), precum şi caracterul "\n".
De multe ori, în C se realizează iniţializarea unor vectori de
caractere a căror dimensiune nu este precizată. Dacă dimensiunea
vectorului nu este precizată, compilatorul C va crea un vector suficient
de lung încât să permită iniţializarea dorită.
Exemplu: În loc să scriem :
char e1[12] = "read error\n";
char e2[13] = "write error\n";
char e3[18] = "cannot open file\n";
putem scrie:
char e1[ ] = "read error\n";
char e2[ ] = "write error\n";

96
char e3[ ] = "cannot open file\n";
Cu această ultimă iniţializare, instrucţiunea
printf ("%s are lungimea %d\n", e2, sizeof (e2));
va tipari:
write error
are lungimea 13
o Iniţializarea unui vector (tablou unidimensional) se poate face şi cu
o listă de iniţializatori scrişi între acolade. Dacă vectorul are o
lungime necunoscută, numărul de iniţializatori determină mărimea
tabloului, iar tipul devine complet. Dacă tabloul are lungime fixă,
numărul de iniţializatori nu poate depăşi numărul de membri din
tablou. În cazul în care sunt mai puţini iniţializatori, membrii în plus
sunt iniţializaţi cu zero. Exemple:
- Instrucţiunea următoare iniţializează un vector de 10 întregi cu
numerele de la 1 la 10:
int i[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
Rezultă că: i[0] = 1, ... , i[9] = 10.
- Instrucţiunea următoare declară şi iniţializează vectorul x ca
un tablou unidimensional cu 3 membri:
int x[] = {1, 2, 3};
- Instrucţiunea următoare:
char sir[6] = { 'h', 'e', 'l', 'l', 'o', '\0' };
este echivalentă cu:
char sir[6] = "hello";

6.1.3. Funcţii pentru prelucrarea şirurilor (fişierul antet


string.h)
• Funcţia strcpy()
Apelul funcţiei strcpy() are următoarea formă generală:
strcpy (nume_sir, constanta_sir);
Funcţia strcpy() copiază conţinutul constantei_sir (inclusiv caracterul
terminator '\n') în nume_sir.
Exemplu:
# include <string.h>
void main(void) {
char sir[80];
strcpy (sir, "hello");
printf("%s", sir); }
Acest program va copia "hello" în şirul sir.
• Funcţia strcat()
Apelul funcţiei strcat() are forma:
97
strcat (s1, s2);
Funcţia strcat() concatenează şirul s2 la sfârşitul şirului s1 şi întoarce
şirul s1. Şirul s2 nu se modifică. Ambele şiruri trebuie să aibă
caracterul terminator NULL, iar rezultatul va avea de asemenea
caracterul terminator NULL.
Exemplu:
# include <stdio.h>
# include <string.h>
void main(void) {
char first[20], second[10];
strcpy (first, "hello");
strcpy (second, "there");
strcat (first, second);
printf ("%s", first); }
Acest program va afişa "hellothere" pe ecran.
• Funcţia strcmp()
Se apelează sub forma:
strcmp (s1, s2);
Această funcţie compară şirurile s1 şi s2 şi returnează valori negative,
dacă s1 < s2, 0, dacă s1 = s2 şi un număr pozitiv, dacă s1 > s2.
Exemplu: Această funcţie poate fi folosită ca o subrutină de verificare
a parolei:
# include <stdio.h>
# include <string.h>
void main (void) {
char s[80];
printf ("Introduceti parola: ");
gets (s);
if (strcmp (s, "pasword")) {
printf (" Invalid pasword \n ");
return 0;}
return 1; }

• Funcţia strlen()
Funcţia strlen() se apelează sub forma:
strlen (s)
unde s este un şir. Funcţia strlen() returnează lungimea şirului s.
Exemplu: Programul următor returnează lungimea unui şir introdus de
la tastatură.
# incude <stdio.h>
# incude <string.h>
void main (void) {
char sir[80];
98
printf ("Introduceti un sir: ");
gets (sir);
printf ("Sirul %s contine %d caractere ", sir,
strlen(sir)); }
Observaţie: Funcţia strlen() nu numără şi caracterul NULL.
Exemplu: Programul următor afişează inversul unui şir de caractere
introduse de la tastatură.
# include <stdio.h>
# include <string.h>
void main (void) {
char sir[80];
int i;
gets(sir);
for(i=strlen(sir)-1;i>=0;i--) printf("%c",sir[i]); }
Exemplu: Programul următor realizează introducerea unor şiruri,
compararea lor, concatenarea lor şi afişarea rezultatului.
# include <stdio.h>
# include <string.h>
void main (void) {
char s1[80], s2[80];
gets(s1); gets(s2);
printf("Lungimi: %d %d \n",strlen(s1),strlen(s2));
if (!strcmp (s1, s2))
printf ("Sirurile sunt egale\n");
strcat (s1, s2);
printf ("%s\n", s1); }
Dacă se rulează acest program şi se introduc şirurile s1 =
"AUTOMATICA" şi s2 = "AUTOMATICA", se va afişa:
Lungimi 10 10
Sirurile sunt egale
AUTOMATICAAUTOMATICA

Dacă şirurile sunt egale, funcţia strcmp() returnează fals (0) şi


din această cauză în instrucţiunea if s-a folosit !strcmp().
Observaţie: Caracterul NULL de terminare a vectorului de caractere
poate fi utilizat în buclele for ca în exemplul următor, unde se
converteşte un şir de caractere scris cu litere mici la litere mari.
# include <stdio.h>
# include <string.h>
void main (void) {
char sir[80];
int i;
strcpy (sir, "acesta este un test");
for(i = 0; sir[i]; i++) sir[i] = toupper (sir[i]);
printf("%s", sir); }

99
Conversia caracterelor se face cu funcţia toupper() care
returnează litera mare corespunzătoare argumentului (literei mici).
Ciclul funcţionează până când sir[i] devine caracterul null.
6.2. Tablouri cu două dimensiuni (matrice)
Tablourile bidimensionale (matricele) sunt reprezentate ca
vectori de vectori. De exemplu, declaraţia:
int v[2][5];
declară un vector cu 2 elemente, fiecare element fiind un vector de tip
int[5]. Se observă că fiecare dimensiune a tabloului este separată
(închisă) între paranteze, iar dimensiunile nu sunt separate prin
virgulă. Astfel, declaraţia: int v[2, 5]; conduce la eroare.
6.2.1. Iniţializarea matricelor
Declaraţia :
char v[2][5] = { 'a', 'b', 'c', 'd', 'e',
'0', '1', '2', '3', '4' };
conduce la iniţializarea primului vector cu primele 5 litere, iar a celui
de-al doilea cu primele 5 cifre.
Exemplu: Programul:
# include <stdio.h>
void main (void) {
char v[2][5] = { 'a', 'b', 'c', 'd', 'e',
'0', '1', '2', '3', '4' };
int i, j;
for (i = 0; i < 2; i++){
for(j = 0; j < 5; j++)
printf ("v[%d][%d] = %c", i, j, v[i][j]);
printf ("\n"); } }
va produce :
v[0][0]=a v[0][1]=b v[0][2]=c v[0][3]=d v[0][4]=e
v[1][0]=0 v[1][1]=1 v[1][2]=2 v[1][3]=3 v[1][4]=4.
Exemplu: Secvenţa de instrucţiuni:
# include <stdio.h>
void main (void) {
int l, c, num[3][4];
for (l = 0; l < 3; ++l)
for (c = 0; c < 4; ++c)
num[l][c] = (l * 4) + c + 1; }
conduce la încărcarea tabloului num[3][4]cu numerele de la 1 la 12.
Astfel, num[0][0] = 1, ..., num[2][3] = 12.

100
Se observă că limbajul C memoreză tablourile bidimensionale
într-o matrice linii-coloane, unde primul indice se referă la linie şi al
doilea indice se referă la coloană. Cantitatea de memorie alocată
permanent pentru un tablou, exprimată în bytes, este:
nr_linii * nr_coloane * sizeof(tipul_datei)
Declaraţia: float y[4][3] = { {1,3,5},
{2,4,6},
{3,5,7},};
este o iniţializare cu paranteze complete şi are următorul efect:
- numerele 1, 3, 5 iniţializează prima linie a tabloului: y[0][0],
y[0][1], y[0][2] sau y[0];
- numerele 2, 4, 6 iniţializează pe y[1];
- numerele 3, 5, 7 iniţializează pe y[2].
Întrucât iniţializatorul se termină cu virgulă, elementele lui y[3] vor fi
iniţializate cu 0. Acelaşi efect ar fi putut fi realizat de:
float y[4][3]={1, 3, 5, 2, 4, 6, 3, 5, 7, };
Secvenţa:
float y[4][3] = { {1}, {2}, {3}, {0}, };
iniţializează prima coloană a lui y, privit ca un tablou bidimensional,
cu 1, 2, 3 şi 0, restul tabloului fiind iniţializat cu 0.
6.2.2. Tablouri bidimensionale de şiruri
Pentru crearea unui tablou de şiruri se foloseşte un tablou de
caractere, bidimensional, în care mărimea indicelui din stânga
determină numărul de şiruri, iar indicele din drepta specifică lungimea
maximă a fiecărui şir. De exemplu, declaraţia :
char sir_tablou[30][80];
defineşte un tablou de 30 de şiruri, fiecare şir având maximum 80 de
caractere. Accesul la un singur şir este foarte uşor: se specifică numai
primul indice. De exemplu:
gets (sir_tablou[2])
întoarce al treilea şir din tabloul sir_tablou. Funcţional, instrucţiunea
anterioară este echivalentă cu:
gets (&sir_tablou[2][0]);

6.3. Tablouri multidimensionale


Forma generală a declaraţiei unui tablou multidimensional este:
tip nume[size1][size2]...[sizeN];
De exemplu, declaraţia:
int trei[4][10][3];

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

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

103
unde pot fi omise fie numele tipului structurii nume_tip_struct, fie
variabile_structura, dar nu ambele. După cum se observă,
nume_tip_structura după cuvântul cheie struct se referă la şablonul
structurii (tipul său) iar variabile_structura se referă la lista de
variabile de acest tip (cu această structură)
Referirea individuală a elementelor unei structuri se face cu
operatorul punct ".". De exemplu, instrucţiunea următoare va atribui
elementului zip al variabilei structură addr_info valoarea 12345:
addr_info.zip = 12345;
Pentru accesarea elementelor unei structuri se foloseşte forma
generală :
nume_structura.nume_element
De exemplu, pentru afişarea variabilei zip se va scrie:
printf ("%d", addr_info.zip);
În aceeaşi formă, se pot referi celelalte elemente ale structurii
addr_info. De exemplu apelul:
gets (addr_info.name);
are ca efect trecerea la un pointer-caracter la primul caracter al
elementului nume. Pentru a avea acces la fiecare caracter al
elementului addr_info.name, se poate indexa name. De exemplu,
pentru afişarea conţinutului lui addr_info.name, caracter cu caracter,
se foloseşte programul:
register int t;
for (t = 0; addr_info.name[t]; ++t)
putchar (addr_info.name[t]);

6.4.1. Tablouri de structuri


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

Tabloul addr_info contine SIZE structuri de tip addr, unde SIZE


se defineşte după necesităţi.
Prima funcţie necesară în program este main(), a cărei structură
este următoarea:
void main() {
char choice;
init_list();
for (;;) {
choice = menu();
switch (choice) {
case 'e' : enter(); break;
case 'd' : display(); break;
case 's' : save(); break;
case 'l' : load(); break;
case 'q' : exit(); }}}
Funcţia init_list() pregăteşte tabloul structură pentru utilizare
prin punerea unui caracter null în primul byte al câmpului "nume".
Programul impune ca o variabilă structură să nu fie utilizată dacă
câmpul nume este vid. Această iniţializare are loc în memoria internă
a calculatorului (nu în fişierul maillist de pe disc).
Structura funcţiei de initializare init_list() ar putea fi următoarea:
/* Functia init_list() */
void init_list() {
register int t;
for (t = 0; t < SIZE; t++)
*addr_info[t].name = '\0'; }

105
Funcţia de selectare a meniului menu() va afişa mesajele
opţiunilor şi va returna varianta aleasă. Prin tastarea literei din
paranteze, se va lansa în execuţie o anumită procedură.
/* Functia menu() */
char menu() {
char s[5],ch;
do {
printf ("(E)nter\n");
printf ("(D)isplay\n");
printf ("(L)oad\n");
printf ("(S)ave\n");
printf ("(Q)uit\n");
printf (" Alegeti optiunea: ");
gets(s);
ch=s[0];
} while (!strrchr("edlsq",ch));
return tolower(ch); }
Observaţie: Funcţia strrchr(cs,c) din <string.h> întoarce un pointer la
ultima apariţie a lui c în cs sau NULL dacă nu apare.
Funcţia enter() are ca efect introducerea unor noi informaţii în
următoarea structură liberă a listei addr_info[SIZE] . Această
introducere se efectuează prin determinarea primei structuri libere din
memorie (cu addr_info.name setată la 0) şi prin completarea sa cu
informaţii culese de la tastatură.
/* Functia enter() */
void enter() {
register int i;
for (i=0; i < SIZE; i++)
if (!*addr_info[i].name) break;
if (i == SIZE) {
printf ("addr_info full \n"); /* Lista plina */
return;}
printf ("Name: ");
gets (addr_info[i].name);
printf ("Street: ");
gets (addr_info[i].street);
printf ("City: ");
gets (addr_info[i].city);
printf ("State: ");
gets (addr_info[i].state);
printf ("Zip: ");
scanf ("%d",&addr_info[i].zip);}
Rutinele save() şi load() se utilizează pentru actualizarea bazei
de date. Aceste rutine lucrează cu fişierul disc maillist. Când se
apelează load(), atunci se copiază în tabloul structură din memorie
106
datele stocate în fişierul maillist. Funcţia load() realizează operaţiunea
inversă, de supraînscriere a fişierului disc cu datele din memorie. Spre
exemplu, dacă dorim să adăugăm date la fişierul maillist care conţine
deja date introduse anterior, ordinea de lansare a comenzilor va fi:
init_list(), load(), enter(), save(), exit(). Structura acestor rutine este
următoarea:
/* Functia save() */
void save() {
register int i;
if ((fp = fopen("maillist", "wb")) == NULL) {
printf (" Cannot open file\n ");
return;}
for (i = 0; i <= SIZE; i++)
if(*addr_info[i].name)
if(fwrite(&addr_info[i],sizeof(struct addr),1,fp) !=1)
printf (" File write error \n ");
fclose (fp);}

/* Functia load() */
void load()
{ register int i;
if ((fp = fopen("maillist","rb")) == NULL) {
printf("Cannot open file\n ");
return;}
for (i = 0; i < SIZE; i++)
if(fread(&addr_info[i],sizeof(struct addr), 1, fp) == 1);
else if (feof(fp)) {
fclose (fp); return;}
else printf ("File read error\n"); }
Atât save() cât şi load() confirmă un succes a unei operaţii cu
fişiere prin verificarea valorilor întoarse de funcţiile fread() sau
fwrite(). În plus, load() trebuie să caute şi indicatorul de sfârşit de
fişier utilizând funcţia feof() deoarece fread() întoarce aceeaşi valoare
dacă se întâlneşte indicatorul de sfârşit de fişier sau dacă apare o
eroare.
Funcţia display() afişează pe ecran întregul tablou structură din
memorie care conţine date valide:
/* Functia display() */
void display() {
register int t;
for (t=0;t<SIZE;t++) {
if (*addr_info[t].name!='\0') {
printf("%s\n",addr_info[t].name);
printf("%s\n",addr_info[t].street);
printf("%s\n",addr_info[t].city);

107
printf("%s\n",addr_info[t].state);
printf("%d\n\n",addr_info[t].zip);
getchar();}}}
Listingul complet al programului va fi:
# include <stdio.h>
# include <ctype.h>
# include <string.h>
# define SIZE 100
struct addr {
char name[20];
char street[30];
char city[15];
char state[10];
unsigned int zip;
} addr_info[SIZE];
FILE *fp;
void init_list(),enter(),save(),load();
void display(),exit();
char menu();

void main() {
char choice;
init_list();
for (;;) {
choice = menu();
switch (choice) {
case 'e' : enter(); break;
case 'd' : display(); break;
case 's' : save(); break;
case 'l' : load(); break;
case 'q' : exit(); }}}
/* Functia init_list() */
void init_list() {
register int t;
for (t = 0; t < SIZE; t++)
*addr_info[t].name = '\0'; }
/* Functia menu() */
char menu() {
char s[5],ch;
do {
printf ("(E)nter\n");
printf ("(D)isplay\n");
printf ("(L)oad\n");
printf ("(S)ave\n");
printf ("(Q)uit\n");
printf (" Alegeti optiunea: ");
gets(s);

108
ch=s[0];
} while (!strrchr("edlsq",ch));
return tolower(ch); }
/* Functia enter() */
void enter() {
register int i;
for (i=0; i < SIZE; i++)
if (!*addr_info[i].name) break;
if (i == SIZE) {
printf ("addr_info full \n"); /* Lista plina */
return;}
printf ("Name: ");
gets (addr_info[i].name);
printf ("Street: ");
gets (addr_info[i].street);
printf ("City: ");
gets (addr_info[i].city);
printf ("State: ");
gets (addr_info[i].state);
printf ("Zip: ");
scanf ("%d",&addr_info[i].zip);}
/* Functia save() */
void save() {
register int i;
if ((fp = fopen("maillist", "wb")) == NULL) {
printf (" Cannot open file\n ");
return;}
for (i = 0; i <= SIZE; i++)
if(*addr_info[i].name)
if(fwrite(&addr_info[i], sizeof(struct addr), 1,fp) !=1)
printf (" File write error \n ");
fclose (fp);}
/* Functia load() */
void load()
{ register int i;
if ((fp = fopen("maillist","rb")) == NULL) {
printf("Cannot open file\n ");
return;}
for (i = 0; i < SIZE; i++)
if(fread(&addr_info[i],sizeof(struct ddr),1,fp)==1);
else if (feof(fp)) {
fclose (fp); return;}
else printf ("File read error\n"); }
/* Functia display() */
void display() {
register int t;

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

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] */

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

111
void f1(struct struct_tip param) /* se declara
functia f1() */
{printf ("%d\n",param.a); }

Pentru exemplificare, propunem următorul program:


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

112
tip_sir(sir_init);
tip_sir(sir_ord);}
// se definesc functiile
struct sir_lung cit_sir()
{int result=1, i=0;
sir.lung=0;
while (result) {
result=scanf("%d",&sir.sir[i]);
i++;}
sir.lung=--i;
return sir;}
void tip_sir(struct sir_lung sirf)
{ int i;
for (i=0;i<sirf.lung;i++)
printf("%d ",sirf.sir[i]);printf("\n");}
struct sir_lung concat_sir(struct sir_lung sir1, struct
sir_lung sir2)
{ int i;
struct sir_lung sir_concat;
sir_concat=sir1;
for (i=0;i<sir2.lung;i++)
sir_concat.sir[sir1.lung+i]=sir2.sir[i];
sir_concat.lung=sir1.lung+sir2.lung;
return sir=sir_concat;}
struct sir_lung ord_sir(struct sir_lung sir1)
{int i,j,temp;
for (i=0;i<sir1.lung;i++)
for (j=i+1;j<sir1.lung;j++)
if (sir1.sir[i]>sir1.sir[j])
{temp=sir1.sir[i];sir1.sir[i]=sir1.sir[j];
sir1.sir[j]=temp;}
return sir=sir1;}
struct sir_lung par_sir(struct sir_lung sir1)
{ int i,j=0;
struct sir_lung sir_par;
for (i=0;i<sir1.lung;i++)
if (!(sir1.sir[i]%2))
{sir_par.sir[j]=sir1.sir[i];j++;}
sir_par.lung=j;
return sir=sir_par;}
struct sir_lung impar_sir(struct sir_lung sir1)
{ int i,j=0;
struct sir_lung sir_impar;
for (i=0;i<sir1.lung;i++)
if (sir1.sir[i]%2)
{sir_impar.sir[j]=sir1.sir[i];j++;}
sir_impar.lung=j;
return sir=sir_impar;}

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

6.4.3. Tablouri şi structuri în structuri


Elementele unei structuri pot fi simple (int, char etc.) sau
complexe (tablouri de elemente de diferite tipuri, structuri).
Exemplu: Considerăm structura :
struct x {
int a[10][10]; /* tablou de 10x10 intregi */
float b;} y;
De exemplu, referirea întregului a[3][7] se face prin:
y.a[3][7]
Când o structură este un element al altei structuri, structurile se
numesc încuibate (nested, incluse).
Exemplu:
struct sal {
struct addr adresa;
float salariu;
} muncitor;
Se observă că elementele variabilei-structură "adresa" sunt
incluse în structura "sal". Aici "addr" este structura definită anterior.
Exemplul anterior defineşte structura "sal" cu 2 elemente: primul este
o structură de tip "addr" care conţine adresa salariatului; al doilea
element este salariul săptămânal al acestuia.
Instrucţiunea următoare va atribui codul 90178 variabilei "zip" din
adresa muncitorului
muncitor.adresa.zip = 90178;
Se observă că elementele fiecărei structuri sunt referite de la
exterior către interior, respectiv de la stânga la dreapta.
6.5. Uniuni
În C o uniune este o zonă de memorie utilizată de mai multe
variabile ce pot fi diferite ca tip. Definitia uniunilor este similară celei
a structurilor.
114
Exemplu:
union tip_u {
int i;
char ch;};
O variabilă de acest tip poate fi declarată fie prin plasarea
numelui său la sfîrşitul acestei definiţii, fie utilizând o instrucţiune de
declarare separată. De exemplu, instrucţiunea :
union tip_u exuniune;
declară variabila-uniune "exuniune" în care atât întregul i, cât şi
caracterul ch ocupă aceeaşi zonă de memorie (cu toate ca int ocupa 4
octeţi, iar char numai un octet).
Când se declară o variabilă union compilatorul C rezervă o zonă
de memorie suficient de lungă capabilă să preia variabila cu lungimea
cea mai mare.
Pentru a accesa elementele unei uniuni se utilizează aceeaşi
sintaxă folosită la structuri (operatorii punct şi " -> ").
Când un element al unei uniuni se adresează direct, se utilizează
operatorul ".", iar când elementul se adresează printr-un pointer, se
foloseşte operatorul "-> ".
De exemplu, pentru a atribui elementul întreg i al uniunii
anterioare "exuniune" valoarea 10, se va scrie:
exuniune.i = 10;
În exemplul următor, programul transferă un pointer la
"exuniune" unei funcţii :
func1(union tip_u *un)
{ un -> i = 10; }
/* se atribuie valoarea 10 intregului i al uniunii exuniune */
In C, uniunile sunt des utilizate când sunt necesare conversii de
tip. De exemplu, funcţia standard putw() ce realizează scrierea binară
a unui întreg într-un fişier de pe disc, poate fi scrisă folosind o uniune.
Pentru aceasta, mai întâi se crează o uniune ce cuprinde un întreg şi un
vector de două caractere, astfel:
union pw {
int i;
char ch[2];};
Atunci, structura lui putw(), utilizând aceasta uniune este :
putw(word, fp)
union pw word; /* se declara uniunea word */
FILE *fp {
putc(word ->ch[0]); // se scrie primul caracter
putc(word->ch[1]); // se scrie al doilea caracter }

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

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

118
count_addr = &count; /*count_addr indica spre count.
Aceasta instructiune plaseaza in count_addr adresa din
memorie a variabilei count, adresa care nu are nici o
legatura cu valoarea lui count */
val = count_addr; /* val preia valoarea de la adresa
count_addr. Aceasta instructiune plaseaza valoarea lui
count aflata la adresa count_addr în variabila val */
printf ("%d", val); /*Se va tipari numarul 100 */ }
Spre exemplu, să 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:
Adresa Memoria
i 1200
j 1202
p 1204

După primele două atribuiri


i = 123;
p = &i;
zona de stocare va arăta astfel:
A dresa M emoria
i 1200 123
j 1202
p 1204 1200

Conţinutul variabilei p (de tip pointer) va fi valoarea 1200, adică


adresa variabilei i.
Instrucţiunea j = *p; copiază un întreg scurt de la locaţia 1200 în
j, locaţiile de memorie fiind acum ca cele din figură:
Adresa Memoria
i 1200 123
j 1202 123
p 1204 1200

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

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

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

o Utilizarea operatorilor de adunare şi de scădere


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

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

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

7.2. Pointeri şi tablouri


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

125
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,

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

7.2.2. Pointeri şi şiruri


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

128
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:

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

Funcţia printf() este apelată din funcţia selmes(). Aceasta va


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

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

7.2.6. Iniţializarea pointerilor


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

131
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 i gh Stiva

M em or ie liber á
pentr u alocar e
(heap)

V ar iabile globale
(statice)

L ow P r ogr am
M em ori a
• A doua metodă de alocare a memoriei, constă în utilizarea
funcţiilor de alocare dinamică malloc() şi free(). Prin această metodă,
un program alocă memorie pentru diversele informaţii în spaţiul
132
memoriei libere numită heap, plasată între programul util şi memoria
sa permanentă şi stivă. Se observă că stiva creşte în jos, iar
dimensiunea acesteia depinde de program.
Un program cu multe funcţii recursive va folosi mult mai intens
stiva în comparaţie cu un program ce nu utilizeaza funcţii recursive,
aceasta deoarece adresele de retur şi variabilele locale corespunzătoare
acestor funcţii sunt salvate în stivă.
Funcţiile malloc() şi free()
Aceste funcţii formează sistemul de alocare dinamică a
memoriei în C şi fac parte din fisierul antet <stdlib.h>. Acestea
lucrează împreună şi utilizează zona de memorie liberă plasată între
codul program şi memoria sa permanentă (fixă) şi vârful stivei, în
scopul stabilirii şi menţinerii unei liste a variabilelor memorate. De
fiecare dată când se face o cerere de memorie, funcţia malloc() alocă o
parte din memoria rămasă liberă. De fiecare dată când se face un apel
de eliberare a memoriei, funcţia free() eliberează memorie sistemului.
Declararea funcţiei malloc() se face sub forma:
void *malloc (int numar_de_bytes);
Aceasta întoarce un pointer de tip void, ceea ce înseamnă că trebuie
utilizat un şablon explicit de tip atunci când pointerul returnat de
malloc() se atribuie unui pointer de un alt tip. Dacă apelul lui malloc()
se execută cu succes, malloc() va returna un pointer la primul byte al
zonei de memorie din heap ce a fost alocată. Dacă nu este suficientă
memorie pentru a satisfce cererea malloc(), apare un eşec şi malloc()
returnează NULL. Pentru determinarea exactă a numărului de bytes
necesari fiecărui tip de date, se poate folosi operatorul sizeof(). Prin
aceasta, programele pot deveni portabile pe o varietate de sisteme.
Funcţia free() returnează sistemului memoria alocată anterior cu
malloc(). După eliberarea memoriei cu free(), aceasta se poate
reutiliza folosind un apel malloc().
Declararea funcţiei free() se realizează sub forma:
free(void *p);
Funcţia free() eliberează spaţiul indicat de p şi nu face nimic dacă p
este NULL. Parametrul actual p trebuie să fie un pointer la un spaţiu
alocat anterior cu malloc(), calloc() sau realloc().
Exemplu: Următorul program va aloca memorie pentru 40 de întregi,
va afişa valoarea acestora, după care eliberează zona, utilizând free():
133
# include <stdio.h>
# include <stdlib.h>
void main(void) {
int t, *p;
p = (int *) malloc(40*sizeof(int));
if (!p) printf("Out of memory \n");
//Verificati neaparat daca p este un pointer corect
else {
for (t=0; t<40; ++t) *(p + t) = t;
for (t=0; t < 40; ++t)
printf("%d", *(p + t));
free(p);
} }

Funcţiile calloc() şi realloc()


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

7.2.8. Pointeri la structuri


Limbajul C recunoaşte pointerii la structuri în acelaşi mod în
care se recunoaşte pointerii la orice alt tip de variabilă.
Declararea unui pointer la structură se face plasând operatorul *
în faţa numelui unei variabile structură. De exemplu, pentru structura
addr definită mai înainte, următoarea instrucţiune declară pe
addr_pointer ca pointer la o dată de acest tip :
struct addr *addr_pointer;
O utilizare importantă a pointerilor la structură constă în
realizarea apelului prin adresă într-o funcţie.
Când unei funcţii i se transmite ca argument un pointer la o
structură, calculatorul va salva şi va reface din stivă numai adresa
structurii, conducând astfel la cresterea vitezei de executare a
programului.

134
Pentru a găsi adresa unei variabile structură, se plasează
operatorul & înaintea numelui variabilei structură. De exemplu,
dându-se următorul fragment :
struct balanta {
float balance;
char name[80];
} person;
struct balanta *p;
/* se declara un pointer la structura */
atunci:
p = &person;
plasează adresa lui person în pointerul p. Pentru a referi elementul
balance, se va scrie:
(*p).balance
Deoarece operatorul punct are prioritate mai mare decât
operatorul *, pentru o referire corectă a elementelor unei structuri
utilizând pointerii sunt necesare paranteze.
Actualmente, pentru referirea unui element al unei variabile
structură dându-se un pointer la acea variabilă, există două metode:
Prima metodă utilizează referirea explicită a pointerului nume-
structură şi este considerată o metoda învechită (obsolete), iar a doua
metodă, modernă, utilizează operatorul săgeată -> (minus urmat de
mai mare).
Exemplu: Pentru a vedea cum se utilizează un pointer-struct,
examinăm următorul program care afişează ora, minutul şi secunda
utilizând un timer software.
# include <stdio.h>
void actualizeaza();
void afiseaza(), delay();
struct tm { /* se defineste structura tm */
int ore;
int minute;
int secunde;};
void main()
{struct tm time; // Structura time de tip tm
time.ore = 0;
time.minute = 0;
time.secunde = 0;
for (;;) {
actualizeaza (&time);
afiseaza (&time); }}
void actualizeaza(t)
/*Versiunea 1- referirea explicita prin pointeri */
struct tm *t; {

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

136
for (;;) {
actualizeaza (&time);
afiseaza (&time); }}
void actualizeaza(t)
/*Versiunea 1- referirea explicita prin pointeri */
struct tm *t; {
t->secunde ++;
if (t->secunde == 60) {
t->secunde = 0;
t->minute ++; }
if (t->minute == 60) {
t->minute = 0;
t->ore ++;}
if (t->ore == 24) t->ore = 0;
delay();}
void afiseaza(t) // Se defineste functia afiseaza()
struct tm *t; {
printf ("%d : ", t->ore);
printf ("%d : ", t->minute);
printf ("%d ", t->secunde);
printf ("\n");}
void delay() /* Se defineşte funcţia delay() */
{ long int t;
for (t = 1;t<140000000;++t);}

7.2.9. Structuri dinamice liniare de tip listă


Structura a fost introdusă ca fiind o grupă (colecţie) ordonată de
date care pot fi de tipuri diferite şi care au anumite legături logice între
ele. Adesea, această grupă de date se repetă de mai multe ori. Se
ajunge astfel la noţiunea de tablou, ale cărui elemente sunt fiecare câte
o structură. Tabloul de date definit în acest fel este şi el de acest tip
nou, pe care îl mai numim şi tip structurat.
După cum s-a remarcat, în exemplele de până acum am folosit
structuri de tip static. Static se referă la faptul că tablourile de structuri
au dimensiuni predefinite. Termenul structuri de date statice exprimă
ideea că putem modifica cu uşurinţă valorile componentelor dar este
foarte dificil să mărim numărul lor peste limita maximă declarată
înainte de compilare. Mai mult, prin ştergerea unor elemente structură
dintr-un tablou de structuri obţinem goluri în tablou, pe care le putem
umple numai printr-o gestiune foarte precisă a poziţiilor din tablou.
Folosind pointeri la tabloul de structuri, este foarte posibil să indicăm
spre un element care a fost şters. Dacă dorim o reprezentare contiguă

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

138
1. Partea de informaţie (info) ce poate aparţine unui tip
simplu (int, char, float, double, etc.) conform cu necesităţile
problemei.
2. Partea de 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.
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

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

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

141
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;
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];
142
unsigned int zip;
struct addr *next;}TNOD;
TNOD *first, *last;
FILE *fp;
extern int create_list();
extern char menu();
extern void display_list(), insert(), erase();
extern int loadf_list(), savef_list();
extern TNOD *add_nod();
Cu ajutorul acestui fişier header se realizează definirea
şablonului structurii addr şi apoi, prin comanda typedef, se defineşte
un nou tip de date numit TNOD. First şi last sunt pointeri la structuri
de acest tip iar fp este pointer la fişier (file pointer).
Cu extern se definesc prototipurile unor funcţii care nu se găsesc în
fişierul bd_main.c ci în fişierul bd_bib.c unde sunt colectate toate
funcţiile pe care le folosim. Acest fişier are la rândul său un fişier
header numit local1.h care conţine:
# include <stdio.h>
# include <ctype.h>
# include <string.h>
# include <stdlib.h>
extern FILE *fp;
typedef struct addr {
char name[20];
char street[30];
char city[15];
char state[10];
unsigned int zip;
struct addr *next;}TNOD;
extern TNOD *first, *last;
Prin cele două fişiere header cele două fişiere sursă bd_main.c
şi bd_bib.c se pot compila împreună, rezultând un singur fişier
executabil.
Interfaţa cu utilizatorul
Aceasta constă într-un meniu principal, care permite accesarea
funcţiilor principale, şi din două submeniuri pentru operaţiile de
ştergere şi inserare de înregistrări.
/* Functia menu() principala */
char menu() {
char s[5],ch;
do {
printf ("\n(C)reate new list\n");
printf ("(L)oad list from file\n");
printf ("(S)ave to file\n");

143
printf ("(D)isplay list\n");
printf ("(I)nsert record\n");
printf ("(E)rase\n");
printf ("(Q)uit\n");
printf (" Alegeti optiunea: ");
gets(s);
ch=s[0];
} while (!strrchr("clsdieq",ch));
return tolower(ch); }
//meniu functia de stergere
char menu_del() {
char s[5],ch;
do {
printf ("(E)ntire list delete\n");
printf ("(F)irst record delete\n");
printf ("(L)ast record delete\n");
printf ("(I)ntermediate delete\n");
printf ("(Q)uit\n");
printf (" Option ? ");
gets(s);ch=s[0];
} while (!strrchr("efliq",ch));
return tolower(ch); }
//meniu functia de inserare
char menu_insert() {
char s[5],ch;
do {
printf ("(F)irst record insert\n");
printf ("(L)ast record insert\n");
printf ("(I)ntermediate insert\n");
printf ("(Q)uit\n");
printf (" Option ? ");
gets(s);ch=s[0];
} while (!strrchr("fliq",ch));
return tolower(ch); }

Încărcarea bazei de date de pe disc


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

144
/* Functia loadf_list() from file */
int loadf_list() {
int n=sizeof(TNOD),i;
TNOD *p;
first=last=NULL;
if ((fp = fopen("maillist","rb")) == NULL) {
printf("Cannot open file\n ");return 0;}
while (((p=(TNOD
*)malloc(n))!=NULL)&&((i=loadf_nod(p))==1))
if (first==NULL){ /* prima creare */
first=last=p;
first->next=NULL;}
else {
last->next=p;last=p;
last->next=NULL;}
if (p==NULL){ printf("Memorie insuficienta pentru
lista\n");
return 0;}
free(p);return i;}
Funcţia loadf_list() alocă fiecare înregistrare citită de pe disc
unui nod al listei dinamice construite în memorie. În pointerii first şi
last se stochează în permanenţă adresele primei şi ultimei înregistrări
(nod sau structură).
Salvarea bazei de date pe disc
Reprezintă operaţiunea inversă celei de citire. Lista simplu
înlănţuită din memorie se salvează în întregime pe disc în fişierul
maillist.dat. Spre deosebire de funcţia loadf_list() care apela la funcţia
loadf_nod(), funcţia savef_list() nu face nici un apel la o altă funcţie
definită de utilizator:
/* Functia savef_list() */
int savef_list() {
TNOD *p;
if ((fp = fopen("maillist", "wb")) == NULL) {
printf (" Cannot open file\n ");return 0;}
p=first;
do {
if(fwrite(p, sizeof(TNOD), 1,fp) !=1)
{printf (" File write error \n ");
fclose (fp);return 0;}
p=p->next;} while (p!=NULL);
fclose (fp);return 1;}

Crearea unei liste simple înlănţuite


La început variabilele first şi last au valoarea NULL, lista fiind
vidă. Crearea unei liste se face prin funcţia create_list. Ea returnează 0
145
pentru eroare şi 1 dacă operaţia reuşeşte. Prin această funcţie se
iniţializează o listă dinamică în memorie, fără a face o citire a unei
baze de date create anterior. Aceasta este prima funcţie care se
apelează când construim o bază de date nouă.
/* Functia create_list() new */
void create_list() {
int n=sizeof(TNOD);
char ch='c';
TNOD *p;
first=last=NULL;
while ((p=(TNOD *)malloc(n))!=NULL)
{p=add_nod(p);
if (first==NULL){ /* prima creare */
first=last=p;
first->next=NULL;}
else {
last->next=p;
last=p;
last->next=NULL;}
printf ("Exit? (x): ");
if ((ch=getchar())=='x') break;}
if (p==NULL){
printf("Memorie insuficienta pentru lista\n");
free(p);}}
Afişarea listei dinamice din memorie
Prin această operaţie, realizată de funcţia display_list(), se
afişează secvenţial toate înregistrările conţinute în nodurile listei
pornind de la prima şi terminând cu ultima.
// functie de afisare antet
void disp_antet() {
printf("\n%20s","Name");
printf("%30s","Street");
printf("%15s","City");
printf("%10s","State");
printf("%5s\n","Zip");}
// afisare o singura inregistrare (nod)
void disp_nod(TNOD *p) {
printf("%20s",p->name);
printf("%30s",p->street);
printf("%15s",p->city);
printf("%10s",p->state);
printf("%5d",p->zip);}
/* Functia display_list() */
void display_list() {
TNOD *p;
disp_antet();

146
p=first;
if (p!=NULL)
do {
disp_nod(p);
p=p->next;
getchar();} while (p!=NULL);
else printf("Lista vida !\n");}
Funcţia display_list() apelează la funcţia disp_nod() care afişeză
o singură înregistrare. Dacă este nevoie de afişarea unui cap de tabel
(antet) se apelează la funcţia disp_antet().
Inserarea unor noduri în lista dinamică
Această operaţie presupune introducerea unor noduri
(înregistrări) fie la începutul listei înaintea primului nod, fie la sfârşitul
său (adăugare) după ultimul nod, fie între două noduri vecine nesituate
la extremităţi. Funcţiile listate mai jos realizează aceste sarcini.
// functia de inserare
void insert() {
char choice;
for (; ;) {
choice = menu_insert();
switch (choice) {
case 'f' : ins_first(); break;
case 'l' : ins_last(); break;
case 'i' : ins_int(); break;
case 'q' : break;} break;}}
/* Functia insert_last() */
void ins_last() {
int n=sizeof(TNOD);
char ch='c',s[2];
TNOD *p;
/* ne pozitionam pe ultimul nod */
p=first;
while (p->next!=NULL) p=p->next;
// se creaza lista in memorie
while (ch!='x') {
p=(TNOD *)malloc(n);
last->next=p;
p=add_nod(p);
p->next=NULL;
last=p;
printf("Exit? (x): ");
gets(s);ch=s[0];}}
/* Functia insert_first() */
void ins_first() {
int n=sizeof(TNOD);
char ch='c',s[2];

147
TNOD *p;
while (ch!='x') {
p=(TNOD *)malloc(n);
p->next=first;
p=add_nod(p);
first=p;
printf("Exit? (x): ");
gets(s);ch=s[0];}}
// inserare dupa inregistrarea afisata
void ins_int() {
TNOD *p, *pi;
char ch='n', s[2];
disp_antet();
p=first;
while ((p!=NULL)&&(ch!='y'))
{ disp_nod(p);
printf("Here ? [y]: ");
gets(s);ch=s[0];
if (ch!='y') p=p->next;}
pi=(TNOD *)malloc(sizeof(TNOD));
pi=add_nod(pi);
pi->next=p->next;
p->next=pi;}
La inserarea unui nod, este nevoie ca acesta să fie legat de cele
între care se introduce cu ajutorul pointerilor next. Dacă se doreşte ca
nodul inserat să fie primul sau ultimul din listă, este nevoie să
modificăm pointerii globali first şi last.
Ştergerea unor noduri din lista dinamică
Nodurile pe care dorim să le ştergem pot fi interioare sau se pot
afla la extremităţi. În acest caz, este nevoie să modificăm pointerii first
şi last . În toate cazurile este nevoie să refacem legăturile distruse
după dispariţia prin ştergere a unor noduri. §tergerea nu este efectivă,
ci numai se refac legăturile şi se eliberează cu funcţia free() zona de
memorie alocată în prealabil (prin inserare sau creare) cu funcţia
malloc().
Mai mult, avem opţiunea de a şterge întreaga listă din memorie
în scopul înlocuirii în totalitate a datelor conţinute în înregistrări.
// funcţia de ştergere
void erase() {
char choice;
for (; ;) {
choice = menu_del();
switch (choice) {
case 'e' : del_list(); break;

148
case 'f' : del_first(); break;
case 'l' : del_last(); break;
case 'i' : del_int(); break;
case 'q' : break;} break;}}
// se sterge intreaga lista si se elibereaza memoria
void del_list() {
TNOD *p,*pu;
p=first;pu=p->next;
while (pu!=NULL) {free(p);
p=pu;pu=pu->next;}
first=NULL;last=NULL;}
// sterge prima inregistrare
void del_first() {
int n=sizeof(TNOD);
char ch='c',s[2];
TNOD *p,*pu;
while (ch!='x') {
p=first;pu=p->next;
free(p);
first=pu;
printf("Exit? (x): ");
gets(s);ch=s[0];}}
// stergere ultima inregistrare
void del_last() {
int n=sizeof(TNOD);
char ch='c',s[2];
TNOD *p;
/* ne pozitionam pe penultimul nod */
while (ch!='x') { p=first;
while (p->next!=last) p=p->next;
free(p->next);
p->next=NULL;
last=p;
printf("Deleted. Exit? (x): ");
gets(s);ch=s[0];}}
// stergere inregistrare intermediara
void del_int() {
TNOD *p, *pa, *pu;
char ch='n', s[2];
disp_antet();
pa=first;p=pa->next;pu=p->next;
while ((p!=last)&&(ch!='y'))
{ disp_nod(p);
printf("Delete ? [y]: ");
gets(s);ch=s[0];
if (ch='y') {pa->next=pu;
free(p);}
else {pa=p;p=pu;pu=pu->next;}}}

149
Capitolul VIII

FUNCŢII

8.1. Forma generală a unei funcţii


Principalul mijloc prin care se pot modulariza programele C
este oferit de conceptul de funcţie (unele funcţii standard au fost deja
folosite pentru diverse operaţii). În C orice funcţie "întoarce"
(returnează), după apel, o valoare al cărui tip trebuie cunoscut. În
practică, însă, de multe ori valorile returnate de funcţii sunt ignorate.
Standardul limbajului C permite chiar declararea explicită a funcţiilor
care nu returnează valori ca fiind de tip void.
În C o funcţie poate fi definită, declarată şi apelată.
Definirea unei funcţii C se realizează după următorul format general:
tip
nume_funcţie (lista_parametri)
declaraţii_parametri
{
declaraţii _locale
instrucţiuni
}
sau, o formă mai nouă adoptată de ANSI-C în 1989:
tip nume_funcţie (declaraţii _parametri)
{
declaraţii _locale
instrucţiuni
}
Tipul unei funcţii corespunde tipului valorii pe care funcţia o va
returna utilizând instrucţiunea return. Acesta poate fi un tip de bază
(char, int, float, double etc.) sau un tip derivat (pointer, structură
etc.).
Dacă pentru o funcţie nu se specifică nici un tip, atunci, implicit
se consideră că funcţia întoarce o valoare întreagă.
Lista parametrilor, lista_parametri, este o listă de nume de
variabile separate prin virgulă care vor primi valorile argumentelor în
momentul apelării acesteia. Tipul acestor parametri este descris fie în
paragraful declaraţii_parametri, fie direct în lista parametrilor. Lista
parametrilor este închisă între paranteze. Chiar dacă o funcţie nu are
parametri, parantezele nu trebuie să lipsească.

150
De exemplu, funcţia max(a, b), care returnează cel mai mare
dintre numerele întregi a şi b, se poate defini sub forma:
int max(a, b) sau int max(int a, int b)
int a, b;
{ {
if (a > b) if (a > b)
return (a); return (a);
else else
return (b); return (b);
} }
În cazul în care tipul parametrilor formali ai unei funcţii nu se
declară, atunci ei sunt consideraţi implicit de tip int. În cazul
compilatoarelor moderne, programul următor va genera două
avertismente.
Exemplu:
# include <stdio.h>
float max(); // Prototipul functiei max()
float x;
void main()
{ x = max(3, 4);
printf("max= %d",x);
}
float max(a, b)
float a, b;
{ if (a > b)
return (a);
else
return (b); }
În urma compilării va rezulta:
Compiling...
test.c
C:\cpp_examples\test.c(11): warning C4244: 'return':
conversion from 'int' to 'float', possible loss of data
C:\cpp_examples\test.c(13): warning C4244: 'return':
conversion from 'int' to 'float', possible loss of data
test.obj - 0 error(s), 2 warning(s)
Aceste avertismente sunt generate deoarece, la primul pas al
compilării, la parcurgerea liniei de declarare a funcţiei:
float max(a,b)
parametrii formali a şi b sunt consideraţi de tip întreg.
Programul, modificat ca mai jos, va duce la o compilare fără
probleme:
Exemplu:
# include <stdio.h>

151
float max();
float x;
void main()
{ x = max(3.2, 4.1);
printf("max= %d\n",x);
}
float max(float a, float b)
{ if (a > b)
return (a);
else
return (b); }
Tot corect va rula şi programul:
# include <stdio.h>
int max();
int x;
void main()
{ x = max(-3,4);
printf("max= %d\n",x);
}
int max(a,b)
{ if (a > b)
return (a);
else
return (b); }
Se observă că nu mai este nevoie de declararea explicită a
parametrilor formali de tip întreg a şi b.
8.2. Reîntoarcerea dintr-o funcţie
Mai intâi precizăm că instrucţiunea return are două utilizări
importante:
♦ return determină ieşirea imediată din funcţia în care se află
instrucţiunea şi reîntoarcerea în programul apelant;
♦ return poate fi folosită pentru a întoarce o valoare.
Reîntoarcerea dintr-o funcţie în programul apelant (funcţia
apelantă) se poate face în două moduri:
a) După parcurgerea codului corespunzător funcţiei se revine în
programul apelant la instrucţiunea imediat următoare. Exemplu:
Aceasta funcţie tipăreşte un şir în ordine inversă:
# include <string.h>
void afis_invers(char s[]);
void main() {
char s[10];

152
printf("Introduceti un sir de caracrere de la
tastatura (max 10)\n");
scanf("%s",s);
afis_invers(s); }
void afis_invers(char s[])
{ register int t;
for (t = strlen(s)-1; t >= 0; t--)
printf("%c", s[t]);
printf("\n"); }
b) Al doilea mod de întoarcere dintr-o funcţie se realizează
utilizând funcţia return. Funcţia return poate fi folosită fără nici o
valoare asociată.
Exemplu: Funcţia următoare afişează rezultatele ridicării unui număr
întreg la o putere întreagă pozitivă:
power (baza, exp){
int baza, exp, i;
scanf("%d %d", &baza, &exp);
if (exp < 0) return; /* Functia se termina
daca exp e negativ */
i = 1;
for (; exp; exp--) i = baza * i;
printf (" Rezultatul este %d \n", i); }
Dacă exponentul exp este negativ, instrucţiunea return
determină terminarea funcţiei înainte ca sistemul să întâlnească }, dar
nu returnează nici o valoare. O funcţie poate conţine mai multe
instrucţiuni return, care pot simplifica anumite algoritme.
8.3. Valori returnate
Toate funcţiile, cu excepţia celor daclarate a fi de tip void,
returnează o valoare. Această valoare este fie explicit specificată prin
return, fie este zero dacă nu se utilizează instrucţiunea return. Dacă o
funcţie este declară ta ca fiind de tip void, aceasta poate fi folosită în
orice expresie C.
O funcţie nu poate fi membrul stâng într-o expresie de atribuire.
De exemplu, instrucţiunea: swap(x,y) = 100; este greşită.
Funcţiile care nu sunt de tip void se pot împărţi în trei categorii:
1) Funcţii "pure" sunt funcţiile care efectuează operaţii asupra
argumentelor şi returnează o valoare de bază pe acea operaţie.
Exemplu: sqrt() şi sin() returnează respectiv radăcina pătrată şi sinusul
argumentului.
2) A doua categorie de funcţii sunt cele care manipulează
informaţii şi întorc o valoare care arată reuşita sau eşecul acestei

153
manipulări. Un exemplu este fwrite() folosită pentru a scrie informaţii
pe disk. Dacă scrierea se face cu succes, fwrite() întoarce numărul de
octeţi înscrişi (ceruţi să se înscrie); orice altă valoare indică apariţia
unei erori.
3) A treia categorie de funcţii sunt cele care nu trebuie să
întoarcă o valoare explicită. De exemplu, funcţia printf() întoarce
numărul de caractere tipărite, număr care, de obicei, nu are o utilizare
ulterioară.
Dacă pentru o funcţie care returnează o valoare nu se specifică o
operaţie de atribuire, calculatorul va ignora valoarea returnată.
Exemplu: Considerăm următorul program care utilizează funcţia
mul():
# include <stdio.h>
mul();
void main (void){
int x, y, z;
x = 10; y = 20;
z = mul(x, y); //- primul apel al lui mul()
printf("%d\n", mul(x,y)); /- al doilea apel al lui mul()
mul(x,y); //- al treilea apel al lui mul() }
mul(a,b) // Se defineste functia mul()
{ return a*b; }
Linia a atribuie valoarea returnată de mul() lui z. În linia b,
valoarea returnată nu este atribuită, dar aceasta este utilizată de
printf(). In linia c valoarea returnată este pierdută, deoarece nu se
atribuie nici unei variabile ce va fi utilizată în altă parte a programului.
8.4. Domeniul unei funcţii
În C, fiecare funcţie este un bloc de instrucţiuni. Codul unei
funcţii este propriu acelei funcţii şi nu poate fi accesat (utilizat) prin
nici o instrucţiune din orice altă funcţie, cu excepţia instrucţiunii de
apel al acelei funcţii. (De exemplu, nu putem utiliza goto pentru a
realiza saltul dintr-o funcţie în mijlocul unei alte funcţii). Blocul de
instrucţiuni care descrie corpul unei funcţii este separat de restul
programului şi dacă acesta nu utilizează variabile globale sau date, el
nici nu poate afecta, nici nu va fi afectat de alte părţi ale programului.
Codul şi datele care sunt definite în interiorul unei funcţii nu pot
interacţiona cu codul şi datele definite în altă funcţie, deoarece cele
două funcţii au scopuri diferite.

154
În cadrul unei funcţii se deosebesc trei tipuri de variabile, astfel:
variabile locale, parametri formali şi variabile globale. Domeniul
unei funcţii determină modul în care alte părţi ale programului pot
avea acces la aceste trei tipuri de variabile stabilind şi durata de viaţă a
acestora.
8.4.1. Variabile locale
Variabilele declarate în interiorul unei funcţii se numesc
variabile locale. Variabilele locale pot fi referite numai prin
instrucţiuni interioare blocului în care au fost daclarate aceste
variabile. Variabilele locale nu sunt cunoscute în afara blocului în care
au fost daclarate, domeniul lor limitându-se numai la acest bloc. Mai
exact, variabilele locale există numai pe durata execuţiei blocului de
cod în care acestea au fost daclarate; deci o variabilă locală este creată
la intrarea în blocul său şi distrusă la ieşire. De obicei, blocurile de
program în care se declară variabilele locale sunt funcţiile. Implicit, o
variabilă locală este auto, deci se stochează în memoria stivă. Ea
poate fi declarată şi register, caz în care se stochează în regiştrii
interni ai microprocesorului sau poate fi declarată static, caz în care se
stochează în memoria de date sau statică, valoarea sa păstrându-se şi
la ieşirea din funcţie.
Exemplu:
func1() {
int x;
x = 10; }
func2() {
int x;
x = -199; }
Aici variabila întreagă x este declarată de două ori, o dată în
func1() şi o dată în func2(). x din func1() nu are nici o legatură cu x
din func2(), deoarece fiecare x este cunoscut numai în blocul în
interiorul căruia a fost declarat.
Limbajul C conţine cuvântul cheie auto, care poate fi folosit
pentru declararea de variabile locale. Cu toate acestea, întrucât C
presupune că toate variabilele neglobale sunt prin definiţie (implicit)
variabile locale, deci au atributul auto, acest cuvânt cheie nu se
utilizează.
De obicei, variabilele locale utilizate în interiorul unei funcţii se
declară la începutul blocului de cod al acestei funcţii. Acest lucru nu
este neapărat necesar, deoarece o variabilă locală poate fi declarată

155
oriunde în interiorul blocului în care se utilizează, dar înainte de a fi
folosită.
Exemplu: Considerăm următoarea funcţie:
func (){
char ch;
printf (" Continuam (y / n) ? : ");
ch = getche(); //Se preia optiunea de la tastatura
/* Daca raspunsul este yes */
if (ch == 'y') {
char s[80];
/* s se creeaza numai dupa intrarea in acest bloc */
printf (" Introduceti numerele: \n ");
gets (s);
prelucreaza_nr (s); /* Se prelucreaza numerele */
} }
Aici, func() creează variabila locală s la intrarea în blocul de cod
a lui if şi o distruge la ieşirea din acesta. Mai mult, s este cunoscută
numai în interiorul blocului if şi nu poate fi referită din altă parte,
chiar din altă parte a funcţiei func() care o conţine.
Deoarece calculatorul creează şi distruge variabilele locale la
fiecare intrare şi ieşire din blocul în care acestea sunt daclarate,
conţinutul lor este pierdut o dată ce calculatorul părăseste blocul.
Astfel, variabilele locale nu pot reţine valorile lor după încheierea
apelului funcţiei.
8.4.2. Parametri formali
Dacă o funcţie va folosi argumente, atunci aceasta trebuie să
declare variabilele care vor accepta (primi) valorile argumentelor.
Aceste variabile se numesc parametri formali ai funcţiei. Parametrii
formali ai funcţiei se comportă ca orice altă variabilă locală din
interiorul funcţiei. Declararea parametrilor formali se face după
numele funcţiei şi înaintea corpului propriu-zis al funcţiei.
Exemplu:
/* Funcţia următoare întoarce 1 dacă caracterul c
aparţine şirului s altfel întoarce 0 */
# include <stdio.h>
int func (char s[10],char c) {
while (*s)
if (*s == c) return 1;
else s++;
return 0; }
void main() {
char s[10], c;
scanf("%c %s", &c, &s);
156
if (func(s, c))
printf("Caracterul se afla in sir\n");
else printf("Caracterul NU se afla in sir\n"); }
Funcţia func() are doi parametri: s şi c. Aceasta funcţie întoarce
1 dacă caracterul c aparţine şirului şi 0 dacă c nu aparţine şirului.
Precizăm că argumentele cu care se va apela funcţia trebuie să
aibă acelaşi tip cu parametrii formali declaraţi în funcţie. Aceşti
parametri formali pot fi utilizaţi ca orice altă variabilă locală.
Un al doilea mod (recomandat de ANSI-C în 1989) de a declara
parametrii unei funcţii constă în declararea completă a fiecărui
parametru în interiorul parantezelor asociate funcţiei. De exemplu,
declararea parametrilor funcţiei func() de mai sus se poate face şi sub
forma:
func (char *s, char c)
{
. . . . . . . . . . .
}

8.4.3. Variabile globale


Spre deosebire de variabilele locale, variabilele globale sunt
cunoscute întregului modul program şi pot fi utilizate de orice parte a
programului. De asemenea, variabilele globale vor păstra valorile lor
pe durata execuţiei complete a programului, deci se stochează în
memoria statică.
Variabilele globale se creează prin declararea lor în afara
oricărei funcţii (inclusiv main()). Variabilele globale pot fi plasate în
orice parte a programului, evident în afara oricărei funcţii, şi înainte de
prima lor utilizare. De obicei, variabilele globale se plasează la
începutul unui program, mai exact înaintea funcţiei main().
Exemplu:
int count; /* count este global */
void main (void) {
count = 100;
func1(); }
func1() /* Se defineste functia func1() */
{ int temp;
/* temp preia variabila globala count */
temp = count;
func2();
printf ("count is %d",count); // Se va afisa 100
}
func2() /* se defineste functia func2() */
{ int count; /* count este local */

157
for (count = 1; count < 10; count ++)
printf ("%2d\n" , count); }
Se observă că, deşi nici funcţia main() şi nici funcţia func1() nu
au declarat variabila count, ambele o folosesc. Funcţia func2() a
declarat o variabilă locală count. Când se referă la count, func2() se va
referi numai la variabila locală count şi nu la variabila globală count
declarată la începutul programului.
Reamintim că, dacă o variabilă globală şi o variabilă locală au
acelaşi nume, toate referirile la numele variabilei în interiorul funcţiei
în care este declarată variabila locală se vor efectua numai asupra
variabilei locale şi nu vor avea nici un efect asupra variabilei globale.
Deci, o variabilă locală ascunde o variabilă globală.
Variabilele globale sunt memorate într-o zonă fixă de memorie
destinată special acestui scop (memoria statică), rămânând în această
zonă pe parcursul întregii execuţii a programului.
Variabilele declarate explicit extern sunt tot variabile globale,
dar accesibile nu numai modulului program în care au fost declarate,
ci şi tuturor modulelor în care au fost declarate de tip extern.
Un alt exemplu util şi pentru înţelegerea lucrului cu pointeri este
următorul: se declară o variabilă globală x, şi apoi două variabile
locale cu acelaşi nume, x, care se vor “ascunde“ una pe cealaltă.
Pentru a vedea şi modul în care se stochează în memorie aceste
variabile, vom afişa şi locaţiile de memorie pentru fiecare tip de
variabilă x, precum şi pentru pointerul p corespunzător.
Reţinem că, în general, dacă:
p = &x => p = &x = &(*p) = (&*)p = p
*p = *(&x) = (*&)x = x
Faptul că * respectiv & sunt operaţiuni complementare (inverse una
celeilalte) se observă din relaţiile de mai sus, din care deducem că:
&* = *& = identitate
&(*z)= z; // z este pointer
*(&z) = z; // z este o variabila de un anume tip
(z este de alt tip în fiecare din egalităţile de mai sus, pointer sau
variabilă).

158
Adresa Memoria
.. .. .. .. .. .. .. .. .

&x=adresa x x
.. .. .. .. .. .. .. ..
.. .. .. .. .. .. .. ..
&p=adresa p p=&x
.. .. .. .. .. .. .. ..

&q=adresa q q=&p

# include <stdio.h>
int x = 34; /* x este global */
void main(void) {
int *p = &x, *r;
/* p este o variabila pointer catre un intreg */
void **q;
printf("x=%d &x=%p *p=%d p=%p &p=%p\n",x,&x, *p, p, &p);
{ int x;
x = 1;
/* Acest prim x este o variabila locala ce o ascunde
pe cea globala */
p = &x;
printf("x=%d &x=%p *p=%d p=%p &p=%p\n",x,&x, *p, p, &p);}
{ int x; /* Acest al doilea x ascunde prima
variabila locala x */
x = 2; // Se atribuie valoarea 2 acestui x
p = &x; /* Pointerul p retine adresa variabilei x */
printf("x=%d &x=%p *p=%d p=%p &p=%p\n",x, &x, *p, p, &p);
q = &p; // q retine adresa pointerului p
r = *q; // r retine valoarea de la adresa q
/*Cum q = &p => r = *(&p) = p => *r = *p = x */
printf("q=%p *q=%p **q=%d &q=%p\n", q, *q, *r, &q); } }
În urma execuţiei programului, obţinem următorul rezultat:
x=34 &x=00426A54 *p=34 p=00426A54 &p=0065FDF4
x=1 &x=0065FDE8 *p=1 p=0065FDE8 &p=0065FDF4
x=2 &x=0065FDE4 *p=2 p=0065FDE4 &p=0065FDF4
q=0065FDF4 *q=0065FDE4 **q=2 &q=0065FDEC
Prin declaraţia int *p = &x; variabila p este declarată
variabilă pointer către o dată de tip întreg şi este iniţializată cu adresa
variabilei spre punctează, anume x. Pointerul q este declarat ca un
pointer la un alt pointer.

159
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
(00426A54 H), pe când în cazurile când indica variabilele locale x se
alocau adresele de memorie 6684140 (0065FDF0 H) şi 6684144
(0065FDEC H), adrese adiacente, la un interval de 4 octeţi, atât cât sunt
alocaţi pentru variabila de tip întreg. Se observă că variabila globală se
află într-o altă zonă de memorie decât variabilele locale.
Modul de lucru cu pointerii este scos în evidenţă prin
instrucţiunile:
q=&p; // q retine adresa pointerului p
r=*q; // r retine valoarea de la adresa q
// q=&p => r = *(&p) = p => *r = *p = x
printf("q=%p *q=%p **q=%d &q=%p\n",q,*q,*r,&q);
prin care se iniţializează pointerul q cu adresa pointerului p, apoi
pointerul r va primi valoarea *q, adică valoarea p.
Una din principalele caracteristici a limbajelor structurate o
constituie compartimentarea codului şi a datelor. În C,
compartimentarea se realizează folosind variabile şi funcţii. De
exemplu, pentru scrierea unei funcţii mul() care determină produsul a
doi întregi se pot utiliza două metode, una generală şi una specifică,
astfel:

160
General : Specific :
mul (x, y) int x, y;
int x, y mul ()
{ return (x * y);} { return (x * y);}
Când se doreşte realizarea produsului a oricăror doi întregi x şi y
se utilizează varianta generală a funcţiei, iar când se doreşte produsul
numai al variabilelor globale x şi y se utilizează varianta specifică.
Exemplu:
# include <stdio.h>
# include <string.h>
int count; // count este global intregului program
play(); // Prototipul pentru functia play()
void main(void) {
char sir[80];
// sir este variabila locala a functiei main()
printf("Introduceti un sir : \n");
gets(sir);
play(sir);
}
play(char *p) // Se declara functia play()
{ // p este local functiei play()
if (!strcmp(p, "add")) {
int a,b; /* a si b sunt locale blocului if din
interiorul functiei play()*/
scanf ("%d %d", &a, &b);
printf ("%d \n", a+b);
}
// int a, b nu sunt cunoscute sau evidente aici
else if(!strcmp(p,"beep")) printf("%c",7); }

8.5. Apelul funcţiilor


Apelul unei funcţii înseamnă referirea funcţiei, împreună cu
valorile actuale ale parametrilor formali, precum şi preluarea valorii
returnate, dacă este necesar. La apelul funcţiei, tipul argumentelor
trebuie să fie acelaşi cu cel al tipului parametrilor formali ai funcţiei.
Dacă apar nepotriviri de tip (de exemplu, parametrul formal al
funcţiei este de tip int, iar apelul funcţiei foloseşte un argument de tip
float) de obicei, compilatorul C nu semnalizează eroare, dar rezultatul
poate fi incorect.
În C transmiterea argumentelor de la funcţia apelantă spre
funcţia apelată se face prin valori sau prin adrese.

161
a) În cazul transmiterii argumentului prin valoare, se realizează
copierea (atribuirea) valorilor fiecărui argument în (la) câte un
parametru formal al funcţiei apelate.
Exemplu: Se apelează o funcţie ce calculeaza pătratul unui număr
întreg.
# include <stdio.h>
square(); // Prototipul functiei sqrt()
void main(void) {
int t = 10;
printf("%d %d\n", t, square(t)); }
square(x) // Se declara functia sqrt()
int x;
{ x = x*x; return(x); }
Se observă că prin această metodă, schimbările survenite asupra
parametrului formal x nu afectează variabila utilizată pentru apelul
funcţiei (schimbările lui x nu modifică în nici un fel pe t).
b) Dacă transmiterea argumentului se realizează prin adrese,
atunci la apelul funcţiei în loc de valori se folosesc adrese, iar în
definiţie, parametrii formali se declară ca pointeri.
Exemplu: O funcţie swap() care schimbă valorile a două
variabile reale se poate defini astfel:
void swap(float *x, float *y){
float temp;
temp = x; /* temp preia valoarea de la adresa x */
*x = *y; /* valoarea de la adresa y este copiata
la adresa x */
y = temp; /* la adresa y se copiaza valoarea
lui temp */
}
Se observă că parametrii formali ai funcţiei swap() sunt pointeri
la float. Programul următor arată modul de apel al acestei funcţii.
# include <stdio.h>
void swap(float *x,float *y);
void main(void) {
float x, y; // x si y sunt de tip float
scanf("%f,%f",&x,&y);/*Se introduc de la tastatura
doua numere reale separate prin virgula*/
printf ("x = %f, y = %f \n ",x,y);
swap(&x,&y); /*Se apeleaza functia swap() avand ca
argumente adresele lui x si y */
printf("x = %f, y = %f \n ",x,y);
}
Prin &x şi &y, programul transferă adresele lui x şi y funcţiei
swap() şi nu valorile lui x şi y.

162
Un apel combinat, valoare-referinţă este prezentat în exemplul
următor:
# include <stdio.h>
void f();
void main (void) {
int x = 1, y = 1;
printf("x = %d, y = %d \n", x, y);
f(x,&y);}
void f(int val, int *ref) {
val++;
(*ref)++;
printf("x = %d, y = %d \n",val,*ref); }

8.6. Apelul funcţiilor având ca argumente tablouri


Când se apelează o funcţie având ca argument un tablou,
acesteia i se va transmite un pointer la primul element al tabloului.
Reamintim că în C numele unui tablou fără nici un indice este un
pointer la primul element al tabloului. Deci, un argument de tipul T[ ]
(vector de tipul T) va fi convertit la T * (pointer de tipul T). Rezultă că
vectorii, ca şi tablourile multidimensionale, nu pot fi transmise prin
valoare. Aceasta înseamnă că declararea parametrului formal trebuie
să fie compatibilă tipului pointer. Există trei moduri de a declara un
parametru care va primi un pointer la un tablou (vector).
a) Parametrul formal poate fi declarat ca un tablou, astfel:
# include <stdio.h>
display(); // Prototipul functiei display()
void main(void) {
int v[10], i;
for (i = 0; i < 10; ++i) v[i] = i;
display(v);}
display(num) // Se defineste functia display()
int num[10];
{ int i;
for (i = 0; i < 10; i++) printf ("%d", num[i]); }
Chiar dacă acest program declară parametrul num ca pe un vector de
10 întregi, compilatorul C va converti automat pe num la un pointer la
întreg, deoarece parametrul nu poate primi întregul tablou (vector).
b) O a doua cale de a declara un parametru vector (tablou),
constă în a specifica parametrul ca pe un vector fără dimensiune:
display(int num[])
{ int i;
for(i = 0; i < 10; i++) printf ("%d",num[i]); }

163
Aceasta funcţie declară pe num ca fiind un vector de întregi cu
dimensiune necunoscută. Deoarece limbajul C nu verifică
dimensiunea vectorilor, dimensiunea actuală a vectorului este
irelevantă ca parametru al funcţiei. §i de aceasta dată, num va fi
convertit la un pointer la întreg.
c) Ultima metodă prin care se poate declara un parametru tablou
este ca pointer, astfel:
display(int *num)
{ int i;
for (i = 0; i < 10; i++) printf ("%d", num[i]); }
Limbajul C permite acest tip de declaraţie deoarece putem indexa
orice pointer utilizând [].
Toate cele trei metode de declarare a unui tablou ca parametru
produc acelaşi rezultat: un pointer. Cu toate acestea, un element al
unui tablou folosit ca argument al unei funcţii va fi tratat ca orice altă
variabilă. Astfel, programul de mai sus poate fi rescris sub forma:
# include <stdio.h>
void main (void) {
int v[10], i;
for (i = 0; i < 10; i++) v[i] = i;
for (i = 0; i < 10; i++) display (v[i]); }
display(int num) { printf ("%d" , num); }
De data aceasta, parametrul din display() este de tip int,
deoarece programul utilizează numai valoarea elementului tabloului.
Exemplu: Vom prezenta un program pentru afişarea tuturor numerelor
prime cuprinse între două limite întregi. Programul principal apelează
două funcţii: nr_prim() returnează 1 dacă argumentul său întreg este
prim şi 0 dacă nu este prim; numerele prime sunt grupate într-un
vector, care se afişează ulterior cu funcţia display().
# include <stdio.h>
int nr_prim(); // Se declara prototipul
void display();
void main (void) {
int a,b,i,j,v[80];
printf("Introduceti limitele: ");
scanf("%d %d", &a, &b);
j = 0;
for (i=a; i<=b; ++i)
if (nr_prim(i)) {v[j]=i; ++j;}
display(v,j);}

int nr_prim(int i) // Decide daca i este prim


{ int j;

164
for (j=2; j<=i/2; j++)
if (i%j==0) return 0;
return 1; }
void display(int *p, int j) /* Tipareste un vector
de intregi */
{ int i;
for (i=0; i<j; ++i) printf("%d ", p[i]); }
Din cele de mai sus, trebuie reţinut că atunci când un tablou se
utilizează ca argument al unei funcţii, calculatorul transmite funcţiei
adresa de început a tabloului. Acest lucru constituie o excepţie a
limbajului C în convenţia de transmitere a parametrilor prin valoare.
Astfel, codul funcţiei poate acţiona asupra conţinutului tabloului şi îl
poate altera.
Exemplu: Programul următor va modifica conţinutul vectorului sir
din funcţia main() după apelul funcţiei afis_litmari().
# include <stdio.h>
# include <ctype.h>
afis_litmari();
void main (void) {
char sir[80];
gets(sir);
afis_litmari(sir);
printf("\n%s\n",sir);}
// Se defineste functia afis_litmari()
afis_litmari(char *s)
{ register int t;
for (t = 0; s[t]; ++t) {
// Se modifica continutul sirului sir
s[t] = toupper(s[t]);
printf("%c",s[t]);}}
Rezultatul rulării programului va fi:
abcdefghijklmnoprstuvxyzw
ABCDEFGHIJKLMNOPRSTUVXYZW
ABCDEFGHIJKLMNOPRSTUVXYZW
Exemplu: Dacă nu dorim să se întâmple acest lucru, programul de mai
sus se poate rescrie sub forma:
# include <stdio.h>
# include <ctype.h>
afis_litmari();
void main (void) {
char sir[80];
gets(sir);
afis_litmari(sir);
printf("\n%s\n",sir);}
afis_litmari(char *s)

165
/* Se defineste functia afis_litmari() */
{ register int t;
for (t = 0; s[t]; ++t) printf("%c",toupper(s[t])); }
//Nu se modifica continutul sirului sir
Rezultatul rulării va fi de această dată:
abcbdefghijklmnoprstuvxyzw
ABCBDEFGHIJKLMNOPRSTUVXYZW
abcbdefghijklmnoprstuvxyzw
În aceasta variantă conţinutul tabloului ramâne nemodificat,
deoarece programul nu-i schimbă valoarea.
Un exemplu clasic de transmitere a tablourilor într-o funcţie îl
constituie funcţia gets() din biblioteca C standard. Prezentăm o
variantă simplificată a acestei funcţii numită xgets().
xgets(s)
char *s; {
char ch;
int t;
for (t = 0; t < 80; ++t) {
ch = getchar();
switch (ch) {
case '\n' :
s[t] = '\0'; /* terminare sir */
return;
case '\b':
if (t > 0) t--;
break;
default:
s[t] = ch; } }
s[80] ='\0'; }
Funcţia xgets() trebuie apelată având ca argument un tablou de
caractere, care, prin definiţie, este un pointer la caracter. Numărul
caracterelor introduse de la tastatură, prin funcţia for este de 80. Dacă
se introduc mai mult de 80 de caractere, funcţia se încheie cu return.
Dacă se introduce un spaţiu, contorul t este redus cu 1. Când se apasă
CR, xgets() introduce terminatorul de şir.

8.7. Argumentele argc şi argv ale funcţiei main()


Singurele argumente pe care le poate avea funcţia main() sunt
argv şi argc.
Parametrul argc conţine numărul argumentelor din linia de
comandă şi este un întreg. Întotdeauna acesta va fi cel puţin 1,
deoarece numele programului este codificat ca primul argument.

166
Parametrul argv este un pointer la un tablou de pointeri la
caractere. Fiecare element din acest tablou indică spre un argument
linie_comanda. Toate argumentele linie_comanda sunt şiruri.
Exemplu: Următorul program arată modul de utilizare al
argumentelor linie_comanda şi va afişa Hello urmat de numele
dumneavoastră, dacă vă introduceţi numele, imediat după numele
programului:
# include <stdio.h>
void main (argc, argv) // Numele programului
int argc;
char *argv[];
{if (argc != 2) {
printf (" Ati uitat sa va introduceti numele \n");
return; }
printf ("Hello %s !", argv[1]); }
Dacă acest program se numeşte ARG_LC.C şi numele
dumneavoastră este DAN, atunci, pentru a executa programul, în linia
de comandă, veţi tipări ARG_LC DAN. Ieşirea programului va fi
Hello DAN !.
Argumentele linie_comanda trebuie separate prin spaţiu sau
TAB şi nu prin virgulă, sau;.
Parametrul argv[] se declară, de obicei, sub forma char
*argv[]; şi reprezintă un tablou de lungime nedeterminată, mai precis
reprezintă un tablou de pointeri. Accesul la elementele lui argv[] se
realizează prin indexarea acestuia, astfel: argv[0] va indica spre primul
şir, care este întotdeauna numele programului; argv[1] va indica spre
primul argument etc. Evitaţi folosirea sa fără paranteze, adică char
*argv.
Următorul program numit "nrinvers" numără invers de la o
valoare specificată prin linia de comandă şi transmite un beep când
ajunge la zero. Precizăm că programul converteşte primul argument,
care conţine numărul la un întreg folosind funcţia standard atoi().
Dacă şirul "display" apare ca al doilea argument_comanda, programul
va afişa, de asemenea, numărul introdus pe ecran.
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
void main(int argc, char *argv[]) /* nrinvers */
{ int disp, count;
if (argc < 2) {
printf ("Trebuie introdusa lungimea numarului
in linia de comanda\n");

167
return; }
if (argc==3 && !strcmp(argv[2], "display"))
disp = 1;
else disp = 0;
for (count = atoi(argv[1]); count; --count)
if (disp) printf("%d ",count);
printf("%c",7); /* Se emite un beep */ }
Observaţie: Dacă în linia de comandă nu se specifică nici un
argument, programul va afişa un mesaj de eroare.

8.8. Funcţii care returnează valori neîntregi


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

Instructiunea de declarare a tipului funcţiei are forma generală:


specificator_de_tip nume_funcţie();
Chiar dacă funcţia are argumente, în declaraţia de tip acestea nu
se precizează (cu excepţia compilatoarelor mai vechi de 1989, care nu
sunt adaptate la cerinţele ANSI-C).
168
Dacă o funcţie ce a fost declarată int întoarce un caracter,
calculatorul converteşte valoarea caracter într-un întreg. Deoarece
conversiile caracter --> întreg-caracter sunt fără probleme, o funcţie ce
întoarce un caracter poate fi definită ca o funcţie care întoarce un
întreg.
8.9. Returnarea pointerilor
Deşi funcţiile care întorc pointeri se manipulează în acelaşi mod
ca şi celelalte tipuri de funcţii, trebuie discutate câteva concepte
importante.
Pointerii la variabile nu sunt nici întregi, nici întregi fără semn.
Pointerii sunt adrese de memorie a anumitor tipuri de date: int, char,
float, double, struct etc.
Motivul acestei distincţii este legat de faptul că atunci când se
prelucrează un pointer aritmetic, această prelucrare este dependentă de
tipul datei indirectate: de exemplu, dacă este increment un pointer la
int, noua valoare (a adresei) va fi cu 4 mai mare faţă de valoarea
anterioară.
În general, când un pointer este incrementat sau decrementat,
acesta va indica către elementul următor, respectiv anterior, din
tabloul pe care îl indirectează. De exemplu, dacă funcţia int f()
returnează un întreg, atunci funcţia int *f(), returnează un pointer la o
dată de tip int.
Deoarece fiecare tip de date poate avea lungimi diferite,
compilatorul trebuie "să ştie" ce tip de dată este indirectată de pointer,
pentru a-l face să indice corect spre următorul element.
Exemplu: Programul următor conţine o funcţie care întoarce un
pointer într-un şir în locul în care calculatorul găseşte o coincidenţă de
caractere.
char *match (char c, char *s)
{int count;
count = 0;
while (c!=s[count] && s[count] != '\0') count ++;
return (&s[count]); }
Funcţia match() va încerca să întoarcă un pointer la locul
(elementul) din şir unde calculatorul găseşte prima coincidenţă cu
caracterul c. Dacă nu se găseste nici o coincidenţă, funcţia va întoarce
un pointer la terminatorul de şir (NULL). Un scurt program ce ar
utiliza funcţia match() este următorul :
# include <stdio.h>

169
# include <conio.h>
char *match(); // Prototipul functiei
void main (void) {
char s[80], *p, ch;
gets (s); /* Se introduce un sir */
ch = getche(); /* Se introduce un caracter */
p = match (ch, s); /* Apelul functiei */
/* p preia valoarea functiei match() */
if (p) {
printf("\n Adresa caracterului ce coincide cu
cel dat este: %p", p);
printf("\n Subsirul de la adresa caracterului
ce coincide cu cel dat este:\n %s\n",p);}
else
printf("Nu exista nici o coincidenta"); }
Acest program citeşte mai întâi un şir şi apoi un caracter. În
cazul în care caracterul este în şir, atunci se tipăreste şirul din punctul
unde se află caracterul, altfel se tipăreşte "Nu există nici o
coincidenţă".
Un caz interesant este oferit de funcţiile care returnează pointeri
către şiruri de caractere. O astfel de funcţie se declară sub forma:
char *f()
Exemplu: Programul următor arată modul în care se defineşte, se
declară şi se apelează o astfel de funcţie.
# include <stdio.h>
void main(void) {
int i;
char *NumeLuna();
scanf("%d", &i);
printf("%s \n ", NumeLuna(i)); }
char *NumeLuna(nr)
int nr;
{ char *luna[]=
{"Eroare", "Ianuarie", "Februarie", "Martie",
"Aprilie", "Mai", "Iunie","Iulie", "August",
"Septembrie", "Octombrie", "Noiembrie",
"Decembrie"};
return ((nr>=1) && (nr <= 12)?luna[nr]:luna[0]); }
Un alt exemplu va fi reprezentat de o variantă a funcţiei strcpy()
din string.h , deci o funcţie care copiază caracterele din şirul s2 în şirul
s1. Rezultatul se găseşte în s1.
/* Vom incepe cu definirea functiei strcpy2() si apoi vom declara
programul principal main(). In acest fel nu mai este necesara declararea
prototipului functiei strcpy2() */
170
# include <stdio.h>
char *strcpy2(register char s1[],register char s2[])
{ char *s0 = s1; // Echivalent: char *s0;s0 = s1;
while ((*s1++ = *s2++) != '\0');
return s0; }
void main()
{ char *sir1,*sir2;
puts(“Introduceti un sir de la tatstatura \n”);
gets(sir2);
puts(strcpy2(sir1,sir2));}
Se observă cum se iniţializează s0 cu s1. Bucla while atribuie
valorile (caracterele) (*s2) în locaţiile indicate de pointerul s1,
incrementând ambii pointeri simultan. Bucla se termină la întâlnirea
caracterului null, care se copiază şi el. Valoarea returnată, s0, reţine
adresa de început a şirului s1.
Un ultim exemplu îl constituie un program de manipulare a
unor matrici. Acest program realizează citirea unei matrici,
transpunerea sa şi respectiv afişarea rezultatului apelând la funcţiile
cit_mat(), trans_mat() şi tip_mat().
# include <stdio.h>
# define DIM_MAX 10
void cit_mat();
void tip_mat();
int *trans_mat();
void main()
{ int a[DIM_MAX][DIM_MAX], dim_lin, dim_col, *p;
printf("Introduceti dimensiunea matricei [dim_lin
dim_col]: ");
scanf("%d %d", &dim_lin, &dim_col);
cit_mat(a, dim_lin, dim_col);
tip_mat(a, dim_lin, dim_col);
p = trans_mat(a, dim_lin, dim_col);
tip_mat(a, dim_col, dim_lin); }

void cit_mat(int p[][DIM_MAX], int lin, int col)


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

void tip_mat(int p[][DIM_MAX], int lin, int col)


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

171
printf("\n"); }
printf("\n"); }

int *trans_mat(int p[][DIM_MAX], int lin, int col)


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

8.10. Funcţii de tip void


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

172
8.11. Funcţii prototip
După cum se ştie, înaintea folosirii unei funcţii care întoarce o
altă valoare decât int, aceasta trebuie definită.
Funcţiile prototip au fost adăugate de comitetul ANSI-C standard.
Declararea unei funcţii prototip se face conform următorului format:
tip nume_funcţie (tip_arg1, tip_arg2,...)
unde: tip = tipul valorii întoarse de funcţie;
tip_arg1, tip_arg2,... = tipurile argumentelor funcţiei.
Exemplu: Programul următor va determina compilatorul să emită un
mesaj de eroare sau de avertisment deoarece acesta încearcă să apeleze
funcţia func() având al doilea argument de tip int, în loc de float, cum
a fost declarat în funcţia func():
#include <stdio.h>
void func(int, float);//Prototipul functiei func()
void main (void) {
int x, y;
x = 10; y = 10;
func (x, y); } /* Se afiseaza o nepotrivire */
void func (x, y) /* Parametrii functiei sunt: */
int x; /* x - intreg */
float y; /* y - real */
{ printf ("%f", y/(float) x); }

Funcţiile prototip se folosesc pentru a ajuta compilatorul în


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

173
Utilizând recomandările ANSI-C din 1989, programul de mai
sus se poate scrie mai compact:
#include <stdio.h>
void func (int x, float y) /* Parametrii formali
includ tipul */
{ printf ("%f", y/(float) x); }
void main (void) {
int x, y;
x = 10; y = 10;
func (x, y); }//afisare avertisment de conversie
sau, folosind funcţia prototip:
#include <stdio.h>
void func(); /* Declarare prototip fara
parametri formali ! */
void main (void) {
int x, y;
x = 10; y = 10;
func (x, y); }
void func (int x, float y) /* Parametrii formali
includ tipul */
{ printf ("%f", y/(float) x); }
În ultimul program am evidenţiat o recomandare care simplifică
efortul de programare în sensul că în linia de declarare a
prototipurilor funcţiilor folosite este necesar să definim tipul funcţiei
nu şi tipul parametrilor formali. Compilatorul se informează despre
tipul parametrilor formali la parcurgerea corpului definiţiei funcţiei.
Din cele de mai sus se observă ca folosirea funcţiilor prototip
ne ajută la verificarea corectitudinii programelor, deoarece nu este
permisă apelarea unei funcţii cu alte tipuri de argumente, decât tipul
celor declarate.

8.12. Funcţii recursive


Funcţiile C pot fi recursive, adică se pot autoapela direct sau
indirect. O funcţie este recursivă dacă o instrucţiune din corpul
funcţiei este o instrucţiune de apel al aceleiaşi funcţii. Uneori o funcţie
recursivă se numeşte şi funcţie circulară.
Un exemplu de o astfel de funcţie este funcţia factorial() care
determină factorialul unui număr. Această funcţie se poate organiza
recursiv, ştiind că: n! = n(n-1)!. Având în vedere 0!=1, această funcţie
se poate organiza astfel:
long factorial (int n) {
if (n == 0) return (1);
174
else
return (n * factorial(n-1)); }
Programul de apel al acestei funcţii se scrie sub forma:
# include <stdio.h>
void main (void) {
int n;
printf("Introduceti un numar intreg : \n");
scanf ("%d, &n);
printf ("(%d) ! = %ld",n,factorial(n)); }
long factorial (int n) {
if (n == 0) return (1);
else
return (n * factorial(n-1)); }
Observaţie: Atunci când o funcţie se autoapelează recursiv, la fiecare
apel al funcţiei se memorează pe stivă atât valorile parametrilor
actuali, cât şi întregul set de variabile dinamice definite în cadrul
funcţiei. Din aceasta cauză stiva trebuie dimensionată corespunzător.
O variantă echivalentă a funcţiei factorial() definită mai sus ar fi
următoarea:
long factorial(int n) {
if (!n) return (1);
else
return (n * factorial (n-1)); }
Un alt exemplu interesant este dat de şirul lui Fibonacci, în care
termenul general an este dat de relaţia de recurenţă: an = an-1+ an-2 ,
unde a0 = 0 şi a1=1. Codul funcţiei poate fi scris sub forma:
long fib(int n) {
if (n == 0)
return (0);
else if (n == 1)
return (1);
else return (fib(n-1)+fib(n-2)); }
Utilizarea recursivităţii poate să nu conducă la o reducere a
memoriei necesare, atât timp cât stiva este folosită intens pentru
fiecare apel recursiv. De asemenea şi execuţia programului poate să nu
fie mai rapidă. Dar codul recursiv este mai compact şi de multe ori
mai uşor de scris şi înţeles decât echivalentul său recursiv.
Recursivitatea este convenabilă în mod deosebit pentru operaţii pe
structuri de date definite recursiv, cum sunt listele, arborii etc.

175
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ă
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

176
declară este activată) sau permanentă (există pe toată durata de
execuţie a programului).
Dacă tipul se declară explicit în declaratorul variabilei, clasa de
memorie se determină prin specificatorul de clasă de memorie şi prin
locul unde se face declaraţia (în interiorul unei funcţii sau înaintea
oricărei funcţii).
Variabilele cele mai folosite sunt cele care sunt declarate în
blocurile aparţinând unei funcţii. Aceste variabile sunt de două feluri:
- auto, aşa cum sunt marea majoritate a variabilelor declarate
numai prin tip. Acesta este un specificator implicit, deci nu este nevoie
să îl invocăm la declararea variabilelor. Variabilele auto sunt plasate
în memoria stivă, iar domeniul de vizibilitate este local, numai pentru
funcţia în care variabila a fost declarată, iar din punctul de vedere al
duratei de viaţă sunt volatile, adică dispar din memoria stivă după
reîntoarcerea din funcţie.
- static, declarate explicit. Variabilele static sunt plasate în
memoria statică, iar domeniul de vizibilitate este local, numai pentru
funcţia în care variabila a fost declarată, iar din punctul de vedere al
duratei de viaţă sunt permanente, adică nu dispar din memoria statică
după reîntoarcerea din funcţie.
- register, declarate explicit. Variabilele register sunt identice
cu cele auto cu excepţia faptului că stocarea nu are loc în memoria
stivă ci în regiştrii interni ai microprocesorului în scopul sporirii
vitezei de execuţie a programelor.
- extern, declarate explicit. Din punct de vedere al modulării
unor programe, este preferabil să divizăm un program complex în mai
multe module program care se leagă în faza de link-editare. O
variabilă declarată extern într-un modul program semnalează
compilatorului faptul că această variabilă a fost declarată într-un alt
modul. Aceste variabile sunt globale, adică sunt văzute de orice modul
de program şi de orice funcţie componentă a unui modul program.
Stocarea are loc în memoria statică iar durata de viaţă este
permanentă, pe toată perioada execuţiei programului.
Iniţializarea unei variabile static diferă de cea a unei variabile
auto prin aceea că iniţializarea este făcută o singură dată, la încărcarea
programului în memorie şi lansarea sa în execuţie. După prima
iniţializare, o variabilă static nu mai poate fi reiniţializată (de
exemplu, la un nou apel al funcţiei în care este iniţializată).

177
Iată ilustrat acest lucru prin două exemple simple. Se tipăreşte,
cu ajutorul funcţiei receip(), un număr care este mai întâi iniţializat cu
valoarea 1 şi returnat incrementat cu o unitate. În cazul folosirii
variabilelor implicite locale auto se rulează programul:
# include <stdio.h>
short receip();
void main(){
printf("First = %d\n",receip());
printf("Second = %d\n",receip());}
short receip()
{ short number = 1;
return number++;}
şi se obţine rezultatul:
First = 1
Second = 1
Dacă se modifică în funcţia receip() variabila number din auto în
static, vom avea
# include <stdio.h>
short receip();
void main(){
printf("First = %d\n",receip());
printf("Second = %d\n",receip());}
short receip()
{ static short number = 1;
return number++;}
şi obţinem rezultatul
First = 1
Second = 2
Limbajul C suportă patru specificatori ai claselor de memorare:
auto, extern, static, register. Aceştia precizează modul de memorare
al variabilelor care îi urmează. Specificatorii de memorare preced
restul declaraţiei unei variabile care capătă forma generală:
specificator_de_memorare specificator_de_tip lista_de_variabile;
Specificatorul auto
Se foloseşte pentru a declară varibilele locale (obiectele dintr-un
bloc). Totuşi, utilizarea acestuia este foarte rară, deoarece, implicit,
variabilele locale au clasa de memorare automată (auto).
Specificatorul extern
Se utilizează pentru a face cunoscute anumite variabile globale
declarare într-un modul de program (fişier) altor module de programe
(fişiere) cu care se va lega primul pentru a alcătui programul complet.
Exemplu:

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

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

179
Variabile globale statice
O variabilă globală cu atributul static este o variabilă globală
cunoscută numai în modulul în care a fost declarată. Deci o variabilă
globală statică nu poate fi cunoscută şi nici modificată din alte module
de program (alte fişiere).
Exemplu:
static int numar_serie;
//var. globala este cunoscuta numai in acest fisier
serie() {
numar_serie = numar_serie + 23;
return (numar_serie); }
/* initializarea variabilei numar_serie */
serie_start(val_init)
int val_init;{
numar_serie = val_init; }
Apelul funcţiei serie_start() cu o valoare intreagă iniţializează
seria generatoare de numere, după care apelul funcţiei serie() va
genera următorul număr din serie.
Specificatorul register
Acest modificator se aplică numai variabilei de tip int şi char.
Acest specificator precizează faptul ca variabilele declarate cu acest
modificator sunt des utilizate şi se pastrează de obicei în registrele
CPU. Specificatorul register nu se aplica variabilelor globale.
Exemplu: Aceasta funcţie calculeaza me pentru întregi :
int_putere (m, e)
int m;
register int e; {
register int temp;
temp = 1;
for (; e; e--) temp * = m;
return temp; }
În acest exemplu au fost declarate ca variabile registru atât e cât
şi temp. De obicei utilizarea unei variabile registru conduce la
micşorarea timpului de execuţie al unui program.
Exemplu :
unsigned int i;
unsigned int delay;
main() {
register unsigned int j;
long t;
t = time ('\0');
for (delay = 0; delay < 10; delay++)
for (i = 0; i < 64000; i++);

180
printf("Timpul pentru bucla non-registru: %ld\n"
,time('\0')-t);
t = time ('\0');
for (delay = 0; delay < 10; delay++)
for (j = 0; j < 64000; j++);
printf ("Timpul bucla registru: %ld",time ('\0')-t);}
Dacă se execută acest program se va găsi că timpul de execuţie
al buclei registru este aproximativ jumătate din timpul de execuţie al
variabilei non-registru.
8.14. Pointeri la funcţii
Într-un fel, un pointer funcţie este un nou tip de dată. Chiar
dacă o funcţie nu este o variabilă, aceasta are o locaţie fizică în
memorie care poate fi atribuită unui pointer. Adresa atribuită
pointerului este punctul de intrare al funcţiei. Acest pointer poate fi
utilizat în locul numelui funcţiei. Pointerul permite de asemenea
funcţiilor să fie pasate (trecute) ca argumente în alte funcţii.
Adresa unei funcţii se obţine utilizând numele funcţiei fără nici o
paranteză sau argumente (ca în cazul tablourilor).
Exemplu:
# include <stdio.h>
# include <ctype.h>
void check();
int strcmp(); /* prototip functie */
void main() {
char s1[80], s2[80];
void *p; /* p preia adresa de intrare a functiei */
p = strcmp;
gets(s1);
gets(s2);
check(s1,s2,p); }

void check (char *a, char *b, int (*cmp) ())


/* cu int (*cmp) () se declara un pointer functie */
{ printf (" Test de egalitate \n ");
if (!(*cmp) (a,b)) printf ("Egal\n");
else printf ("Neegal\n"); }
Declararea lui strcmp() în main() s-a facut din două motive:
1) programul trebuie să ştie ce tip de valoare returnează strcmp();
2) numele trebuie cunoscut de compilator ca şi funcţie.
Deoarece în C nu există o modalitate de a declara direct un
pointer funcţie, acesta se declară indirect folosind un pointer void care
poate primi orice fel de pointer.
181
Apelul funcţiei check() se face având ca parametri doi pointeri la
caracter şi un pointer funcţie.
Instrucţiunea : (*cmp)(a, b)
realizează apelul funcţiei, în acest caz strcmp() iar a şi b sunt
argumentele acestuia.
Exemplu:
# include <stdio.h>
# include <ctype.h>
int strcmp(); /* prototip functie */
void main() {
char s1[80], s2[80];
int (*p)(); /* p este pointer la functie */
p = strcmp;
gets (s1);
gets (s2);
printf (" Test de egalitate \n ");
if (!(*p) (s1,s2)) printf ("Egal\n");
else printf("Neegal\n"); }
Observaţie:
Funcţia check() poate utiliza direct funcţia strcmp() sub forma:
check (s1, s2, strcmp);
Exemplu:
# include <stdio.h>
# include <ctype.h>
void check ();
int strcmp(); /* prototip functie */
void main() {
char s1[80], s2[80];
gets (s1);
gets (s2);
check (s1, s2, strcmp); }

void check (char *a, char *b, int (*cmp) ())


// se defineste functia check()
/* cu int (*cmp) () se declara un pointer functie */
{ printf (" Test de egalitate \n ");
if (!(*cmp) (a,b)) printf ("Egal\n");
else printf ("Neegal\n"); }

182
Capitolul IX

PREPROCESAREA

Un preprocesor C realizează substituirea macrodefiniţiilor, o


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

9.1. Directive uzuale


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

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

184
Directiva #error
Directiva #error forţează compilatorul să stopeze operaţia de
compilare când această este intilnita în program. Este utilizata în
primul rind pentru depanarea programelor. Forma generală a directivei
este: #error mesaj_de_eroare
Aceasta linie determină procesorul să scrie mesajul de eroare şi
să termine compilarea.
Directiva # include
Directiva # include comandă compilatorului să includă în
fişierul ce conţine directiva #include un alt fişier sursă al cărui nume
este specificat în directivă. Formele directivei sunt :
# include <nume_fisier>
# include "nume_fisier"
Prima formă se referă la fişiere header (cu extensia .h) care se
găsesc în subdirectorul include din fiecare mediu de programare C, iar
cea de-a doua la fişiere header create în directorul de lucru al
utilizatorului (directorul curent). Directivele # include pot fi folosite şi
una în interiorul celeilalte.
9.2. Directive pentru compilare condiţionată
Limbajul C conţine câteva directive care ne permit să compilăm
selectiv anumite porţiuni de program.
Directivele #if, #else, #elif şi #endif
Forma generală a lui #if este:
#if expresie_constanta
secventa de instructiuni
#endif
Dacă expresie_constanta este adevărată, compilatorul va
compila fragmentul de cod cuprins între #if şi #endif, iar dacă
expresie_constanta este falsă, compilatorul va sări peste acest bloc.
Exemplu:
#define MAX 100
void main() {
#if MAX > 99
printf("Se compileaza pentru tablouri > 99\n");
#endif }
Observaţie: Expresie_constanta se evaluează în timpul compilării. De
aceea, aceasta trebuie să conţină numai variabile constante definite
anterior utilizării lor. Expresie_constanta nu trebuie să conţină
operatorul sizeof.
185
Directiva #else lucrează similar cu instrucţiunea else
determinând o alternativă de compilare. Exemplu :
# define MAX 10
void main() {
#if MAX > 99
printf("Se compileaza pentru tablouri > 99\n");
#else
printf("Se compileaza pentru tablouri < 99\n");
#endif }
Deoarece MAX = 10, compilatorul va compila numai codul
cuprins între #else şi #endif, deci va tipări mesajul :
Se compilează pentru tablouri < 99
Directiva #elif inlocuieşte "else if" şi este utilizată pentru
realizarea opţiunilor multiple de tip if / else / if utilizate la compilare.
Forma generală a directivelor #if , #elif, #endif este:
#if expresie
Secventa_de_instructiuni
#elif expresie_1
Secventa_de_instructiuni_1
#elif expresie_2
Secventa_de_instructiuni_2
. . . . . . . . . . . . . .
#elif expresie_N
Secventa_de_instructiuni_N
#endif
Dacă "expresie" este adevărată se compilează
"Secventa_de_instructiuni" şi nu se mai tastează nici o altă expresie
#elif. Dacă "expresie" este falsă, compilatorul verifică următoarele
expresii în serie, compilându-se "Secventa_de_instructiuni_i",
corespunzatoare primei "expresie_i" adevărată, i = 1, 2, . . . , N.
Directivele #if şi #elif se pot include unele pe altele. Exemplu:
#if MAX > 100
#if VERSIUNE_SERIALA
int port = 198;
#elif
int port = 200;
#endif
#else
char out_buffer[100];
#endif

Directivele #ifdef şi #ifndef


O altă metodă de compilare condiţionată utilizează directivele
#ifdef şi #ifndef, care înseamnă "if defined" şi "if not defined".

186
Forma generală a lui #ifdef este :
#ifdef macro_name
Secventa_de_instructiuni
#endif
Dacă anterior apariţiei secvenţei de mai sus s-a definit un
macro_name printr-o directivă #define, compilatorul va compila
"Secventa_de_instructiuni" dintre #ifdef şi #endif.
Forma generală a lui #ifndef este:
#ifndef macro_name
Secventa_de_instructiuni
#endif
Dacă macro_name nu este definit prîntr-o directivă #define,
atunci se va compila blocul dintre #ifndef şi #endif.
Atât #ifdef, cât şi #ifndef pot utiliza un #else, dar nu #elif.
Exemplu:
# define TOM 10
void main() {
#ifdef TOM
printf("Hello TOM !\n");
#else
printf("Hello anyone !\n");
#endif
#ifndef JERY
printf ("Jery not defined \n");
#endif }
Programul va afişa: Hello TOM ! şi JERY not defined.
Dacă nu s-a definit TOM, atunci programul va afişa : Hello anyone !.
Directiva #undef
Se utilizează pentru a anula definiţia unui macro_name definit
printr-o directivă #define.
Exemplu:
#define LENGTH 100
#define WIDTH 100
char array[LENGTH][WIDTH];
#undef LENGTH
#undef WIDTH
Acest program defineşte atât LENGTH, cât şi WIDTH până se
întâlneşte directiva #undef.
Principala utilizare a lui #undef este de a permite localizarea
unui macro_name numai în anumite secţiuni ale programului.
Directiva #line
O linie cu una din formele:
187
#line numar "nume_fiaier"
#line numar
determină compilatorul să considere, din motive de diagnosticare a
erorilor, că numărul de linie al urmatoarei linii din programul sursă
este dat de "număr", iar numele fişierului în care se află programul
sursă este dat de "nume_fişier". Dacă lipseste "nume_fişier",
programul sursă se află în fişierul curent.
Exemplu:
Următoarea secvenţă face ca numărul de linie să înceapă cu 100.
# line 100
void main() /* linia 100 */
{ /* linia 101 */
printf ("%d\n" , __LINE__); /* linia 102 */
}
Instructiunea printf() va afişa valoarea 102 deoarece această
reprezintă a treia linie în program, după instrucţiunea #line 100.
Directiva #pragma
O linie de control de forma:
#pragma nume
determină compilatorul să realizeze o acţiune care depinde de modul
de implementare al directivei #pragma. "nume" este numele acţiunii
#pragma dorite.
Limbajul C defineşte două instrucţiuni #pragma: warn şi inline.
Directiva warn determină compilatorul să emită un mesaj de
avertisment. Forma generală a lui warn este :
#pragma warn mesaj
unde "mesaj" este unul din mesajele de avertisment definite în C.
Forma generală a directivei inline este :
#pragma inline
şi avertizează compilatorul că programul sursă conţine şi cod în
limbajul de asamblare.
Directiva vidă
O linie de forma:
#
nu are nici un efect.
Macro_names (macrosimboluri) predefinite
Limbajul C conţine câţiva identificatori predefiniţi, care la
compilare se expandează pentru a produce informaţii speciale.
Aceştia sunt:

188
__LINE__ o constanta zecimală care conţine numele liniei
sursă curente.
__FILE__ un şir care conţine numele fişierului care se
compilează.
__DATA__ un şir care conţine data compilării sub forma
luna/zi/an.
__TIME__ un şir care conţine ora compilării sub form:
hh:mm:ss
__STDC__ constanta 1. Acest identificator este 1 numai în
implementarile standard; dacă constanta este orice alt număr, atunci
implementarea este diferită de cea standard.
Aceste macrosimboluri, împreună cu simbolurile definite cu
#define nu pot fi redefinite.
9.3. Modularizarea programelor
De obicei (vezi [Mocanu, 2001] programele C constau din
fişiere sursă unice, cu excepţia fişierelor header. Un singur fişier sursă
este în general suficient în cazul programelor mici.
Modularizarea internă este un principiu de bază al programării
în C şi constă în utilizarea pe scară largă a funcţiilor definite de
utilizator. Scrierea programului principal (main) se concentrează mai
ales pe apelul acestor funcţii. În cazul în care corpul de definiţie al
funcţiilor utilizator se află după corpul de definiţie main, este necesar
ca să declarăm prototipul funcţiilor utilizate de main() pentru a
informa corect compilatorul despre tipul variabilelor returnate de
funcţii. O altă modalitate este aceea de a defini funcţiile utilizator
înaintea funcţiei principale main(), caz în care nu mai sunt necesare
prototipurile. Programul este modularizat cu ajutorul funcţiilor prin
divizarea sa în nuclee funcţionale. Acestea pot fi comparate cu nişte
mici piese de lego cu ajutorul cărora se pot construi ulterior structuri
(programe) foarte complexe.
Pe scurt, modularizarea internă constă în descompunerea
sarcinii globale a unui program în funcţii de prelucrare distincte.
O funcţie de uz general este o funcţie care poate fi folosită într-
o varietate de situaţii şi, probabil, de către mai mulţi utilizatori. Este
de preferat ca aceste funcţii de uz general să nu primească informaţii
prin intermediul unor variabile globale ci prin intermediul
parametrilor. Sporeşte astfel foarte mult flexibilitatea în folosirea
acestor funcţii.
189
Modularizarea externă constă în divizarea unui program foarte
complex în mai multe subprograme. Astfel, un fişier sursă mai mare se
poate diviza în două sau mai multe fişiere sursă mai mici. Evident,
aceste fişiere sunt strâns legate între ele pentru a forma în final un tot
unitar echivalent cu programul complex iniţial (dinainte de divizare).

În figura de mai sus se prezintă un ecran al Microsoft Visual


C++ din MSDN 6.0
Noţiunea cea mai cuprinzătoare este aceea de Workspace (spaţiu
de lucru) care cuprinde în esenţă o colecţie de proiecte corelate şi
prelucrabile împreună. Un workspace cuprinde unul sau mai multe
Projects (proiecte) dintre care numai unul este principal şi restul sunt
subordonate (subprojects). Fiecare proiect este compus la rândul său
din mai multe fişiere, de acelaşi tip sau de tipuri diferite.
Prezentarea exhaustivă a organizării acestui mediu de dezvoltare
a aplicaţiilor C/C++ este un demers în afara prezentei lucrări. Ceea ce
merită să subliniem este faptul că, în cadrul cel mai întâlnit, anume un
workspace care include un singur project, acest proiect conţine mai
ales fişiere sursă şi fişiere de tip header. Aceste fişiere se numesc
module. Modulul principal este fişierul care conţine funcţia principală
main(). Celelalte fişiere sursă, dacă există, se numesc module

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

191
Modulul bd_bib.c este:
# include "local1.h"
/* Functia init_list() */
void init_list() {
register int t;
for (t = 0; t < SIZE; t++)
*addr_info[t].name = '\0'; }

/* Functia menu() */
char menu() {
char s[5],ch;
do {
printf ("(E)nter\n");
printf ("(D)isplay\n");
printf ("(L)oad\n");
printf ("(S)ave\n");
printf ("(Q)uit\n");
printf (" Alegeti optiunea: ");
gets(s);
ch=s[0];
} while (!strrchr("edlsq",ch));
return tolower(ch); }

/* Functia enter() */
void enter() {
register int i;
for (i=0; i < SIZE; i++)
if (!*addr_info[i].name) break;
if (i == SIZE) {
printf ("addr_info full \n"); /* Lista plina */
return;}
printf ("Name: ");
gets (addr_info[i].name);
printf ("Street: ");
gets (addr_info[i].street);
printf ("City: ");
gets (addr_info[i].city);
printf ("State: ");
gets (addr_info[i].state);
printf ("Zip: ");
scanf ("%d",&addr_info[i].zip);}

/* Functia save() */
void save() {
register int i;
if ((fp = fopen("maillist", "wb")) == NULL) {
printf (" Cannot open file\n ");
return;}

192
for (i = 0; i <= SIZE; i++)
if(*addr_info[i].name)
if(fwrite(&addr_info[i],sizeof(struct addr),1,fp) !=1)
printf (" File write error \n ");
fclose (fp);}
/* Functia load() */
void load()
{ register int i;
if ((fp = fopen("maillist","rb")) == NULL) {
printf("Cannot open file\n ");
return;}
for (i = 0; i < SIZE; i++)
if(fread(&addr_info[i],sizeof(struct addr),1,fp)==1);
else if (feof(fp)) {
fclose (fp); return;}
else printf ("File read error\n"); }

/* Functia display() */
void display() {
register int t;
printf("\n%20s","Name");
printf("%30s","Street");
printf("%15s","City");
printf("%10s","State");
printf("%5s\n","Zip");
for (t=0;t<SIZE;t++) {
if (*addr_info[t].name!='\0') {
printf("%20s",addr_info[t].name);
printf("%30s",addr_info[t].street);
printf("%15s",addr_info[t].city);
printf("%10s",addr_info[t].state);
printf("%5d",addr_info[t].zip);
getchar();}}}
Fişierul local1.h conţine:
# include <stdio.h>
# include <ctype.h>
# include <string.h>
# define SIZE 100
extern struct addr {
char name[20];
char street[30];
char city[15];
char state[10];
unsigned int zip;
} addr_info[SIZE];
extern FILE *fp;
Se poate verifica cum fiecare modul în parte este compilabil fără
erori, iar la link-editare nu se semnalează, de asemenea, erori.
193
Capitolul X

INTRĂRI/IEŞIRI

10.1. Funcţii de intrare şi ieşire - stdio.h


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

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

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

10.2. Operaţii cu fişiere


În acest subcapitol vom detalia principalele operaţii efectuate
asupra unor fişiere.
În timpul lucrului cu fişierele, sistemul de operare păstrează un
indicator de fişier care indică poziţia curentă în fişier, poziţie la care se
va face următoarea operaţie de scriere sau citire. De exemplu, la
deschiderea unui fişier pentru citire indicatorul de fişier va indica la
începutul fişierului. Dacă se va face o operaţie de citire a 2 octeţi se
vor citi octeţii cu numărul de ordine 0 şi 1 iar indicatorul va indica
spre următorul octet, adică cel cu numărul de ordine 3.
Pentru o mai corectă înţelegere a acestor funcţii le vom
structura după nivelul la care se utilizează: inferior sau superior.
În momentul începerii execuţiei unui program, interfeţele
standard (cu ecranul, tastatura şi porturile seriale şi paralele) sunt
deschise în mod text.
Principalele funcţii sunt grupate în tabelul de mai jos:

197
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
• 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

198
VAX sau U3B, pe majoritatea implementărilor tipul FILE se defineşte
prin următoarea structură:
typedef struct {
unsigned char *_ptr;
int _cnt;
unsigned char *_base;
char _flag;
char _file;
} FILE;
Variabila de tip FILE este creată şi gestionată de către suportul
pentru exploatarea fişierelor în limbajul C. În urma deschiderii unui
fişier, programul primeşte un pointer la variabila creată, deci un
pointer la o structură de tip FILE. Se spune că s-a deschis un stream
(flux de date). Toate operaţiile care se fac pe acest stream se referă la
fişierul asociat stream-ului.
În limbajul C există 5 stream-uri standard, definite în <stdio.h>:
FILE *stdin;
care se referă la dispozitivul standard de intrare (tastatura). Orice
operaţie de citire de la stream-ul stdin înseamnă citire de la tastatură.
Bufferul folosit are o dimensiune de 254 de caractere şi bufferul se
goleşte la tastarea NL (‘\n’). Se mai spune că stdin este cu buffer la
nivel de linie.
FILE *stdout;
care se referă la dispozitivul standard de ieşire (ecranul). Orice
operaţie de scriere la stream-ul stdout înseamnă scriere pe ecran. Spre
deosebire de stdin, stdout este ne-bufferizat deoarece orice scriere pe
ecran se face direct la scrierea unui caracter în fişierul stdout.
FILE *stderr;
care se referă la dispozitivul standard pentru afişarea mesajelor de
eroare (ecranul). Este ne-bufferizat.
FILE *stdprn;
care se referă la primul port paralel PRN la care se conectează de
obicei imprimanta (LPT). Este bufferizat la nivel linie.
FILE *stdaux;
care se referă la primul port serial COM1. Este ne-bufferizat.
10.3. Nivelul inferior de prelucrare a fişierelor
La acest nivel operaţiile de prelucrare a fişierelor se execută fără
o gestiune automată a zonelor tampon, făcându-se apel direct la
sistemul de operare. Programatorul are în gestiune o zonă declarată
199
drept buffer şi trebuie să ţină cont de faptul că această bufferizare este
la nivel linie. Numele funcţiilor de nivel inferior, orientate pe text
(transfer de octeţi) încep de obicei cu _ (underline). Dacă un fişier se
deschide în modul text, atunci, în cazul citirii dintr-un fişier, secvenţa
de octeţi CR-LF (0DH, 0AH) este translatată (înlocuită) cu un singur
caracter LF, iar în cazul scrierii în fişier caracterul LF este expandat la
secvenţa CR-LF. De asemenea, în cazul MS-DOS sau Windows
CTRL/Z este interpretat în cazul citirii drept caracter de sfârşit de
fişier (EOF).
10.3.1. Deschiderea unui fişier
Orice fişier înainte de a fi prelucrat trebuie deschis, motiv
pentru care operaţia de deschidere a unui fişier este de mare
importanţă. Deschiderea unui fişier existent se realizează prin
intermediul funcţiei _open. La revenirea din ea se returnează un aşa
numit descriptor de fişier. Acesta este un număr întreg. El identifică în
continuare fişierul respectiv în toate operaţiile realizate asupra lui.
În forma cea mai simplă funcţia _open se apelează printr-o
expresie de atribuire de forma:
df = _open(spf,mod)
unde:
df – este un număr întreg care reprezintă descriptorul de fişier
spf – este specificatorul fişierului care se deschide
mod – defineşte modul de prelucrare a fişierului
Specificatorul de fişier este fie un şir de caractere, fie un pointer
spre un astfel de şir de caractere. Conţinutul şirului de caractere
depinde de sistemul de operare folosit. În cea mai simplă formă el este
un nume sau mai general o cale care indică plasamentul pe disc al
fişierului care se operează. Fişierele deschise la acest nivel pot fi
prelucrate în citire (consultare), scriere (adăugare de înregistrări) sau
citire/scriere (actualizare sau punere la zi).
Calea spre fişier trebuie să respecte convenţiile sistemului de
operare MS-DOS în general. În cea mai simplă formă ea este un şir de
caractere care defineşte numele fişierului, urmat de extensia fişierului.
Aceasta presupune că fişierul se găseşte în directorul curent. Dacă
fişierul nu se află în fişierul curent, atunci numele este precedat de o
construcţie de forma:
litera:\nume_1\nume_2\…\nume_k
unde:

200
litera – defineşte discul (în general A, B pentru floppy-disk şi C, D,..
pentru hard-disk)
nume_i – este un nume de subdirector.
Deoarece calea se include între ghilimele, caracterul ‘\’ se
dublează. Spre exemplu, putem folosi o comandă de deschidere de
forma:
int d;
d=_open(“A:\\JOC\\BIO.C“,O_RDWR);
caz în care fişierul BIO.C din directorul JOC de pe dscheta A se
deschide în citire/scriere.
În funcţie de operaţia dorită, mod poate avea valorile:
0 - pentru citire
1 - pentru scriere
2 - pentru citire/scriere
Deschiderea unui fişier nu reuşeşte dacă unul dintre parametri este
eronat. În acest caz funcţia _open returnează valoarea (-1).
int _open( const char *filename, int oflag [, int pmode] );
este definiţia generală a funcţiei _open.
Modul de acces mod se poate furniza în mod explicit printr-o
variabilă de tip întreg (oflag) care poate avea valorile:
Variabila mod Modul de deschidere a fişierului
_O_RDONLY Fişierul se deschide numai în citire (read-only)
Nu se poate specifica împreună cu _O_RDWR sau
_O_WRONLY
_O_WRONLY Fişierul se deschide numai în scriere (write-only) Nu se
poate specifica împreună cu _O_RDWR sau
_O_RDONLY
_O_RDWR Fişierul se deschide în citire/scriere (read/write)

_O_APPEND Fişierul se deschide pentru adăugarea de înregistrări la


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

_O_TEXT Fişierul este de tip text, adică se prelucrează pe


caractere sau octeţi (implicit)

201
Menţionăm că în MSDN aceste variabile se mai numesc şi oflag
(open-flag) şi sunt definite în fişierul header FCNTL.H.
În cazul în care oflag este _O_CREAT, atunci este necesară
specificarea constantelor opţionale pmode, care se găsesc definite în
SYS\STAT.H. Acestea sunt:
_S_IREAD - este permisă numai citirea fişierului
_S_IWRITE - este permisă şi citirea (permite efectiv
citirea/scrierea fişierului)
_S_IREAD | _S_IWRITE - este permisă şi scrierea şi citirea
fişierului.
Argumentul pmode este cerut numai când se specifică
_O_CREAT. Dacă fişierul există deja, pmode este ignorat. Altcumva,
pmode specifică setările de permisiune asupra fişerului care sunt
activate când fişierul este închis pentru prima oară.
_open aplică masca curentă de permisiune la fişier înainte de
setarea accesului la fişier.
Pentru a crea un fişier nou se va utiliza funcţia _creat pentru a-l
deschide. De fapt se deschide prin creare un fişier inexistent. Funcţia
este definită astfel:
int _creat( const char *filename, int pmode );
în care parametrii au fost descrişi mai sus.
Protecţia unui fişier este dependentă de sistemul de operare.
Spre exemplu, în UNIX protecţia se defineşte prin 9 biţi ataşaţi
oricărui fişier, grupaţi în 3 grupe de câte 3 biţi. Fiecare bit controlează
o operaţie de citire, scriere, execuţie. Protecţia operaţiilor se exprimă
faţă de proprietar, grup sau oricine altcineva. Numărul octal 0751
permite proprietarului toate cele 3 operaţii indicate mai sus (7 = 1112),
grupul la care aparţine proprietarul poate citi şi executa fişierul (5 =
1012) iar alţi utilizatori pot numai executa fişierul (1 = 0012). Funcţia
_creat poate fi apelată şi în cazul în care se deschide un fişier deja
existent, caz în care se pierde conţinutul vechi al fişierului respectiv şi
se crează în locul lui unul nou cu acelaşi nume.
Fiecare din funcţiile _open sau _creat returnează un
specificator de fişier (handle) pentru fişierul deschis. Acest
specificator este o valoare întreagă pozitivă. Implicit, stdin are
specificatorul 0, stdout şi stderr au specificatorii 1 respectiv 2 iar
fişierele disc care sunt deschise primesc pe rând valorile 3, 4,..etc.
până la numărul maxim admis de fişiere deschise.

202
Valoarea returnată -1 indică o eroare de deschidere, în care caz
variabila errno este setată la una din valorile:
EACCES – (valoare 13) s-a încercat deschiderea pentru scriere a
unui fişier read-only sau modul de partajare a fişierului nu permite
operaţia specificată sau calea nu specifică un nume de fişier ci de
director.
EEXIST – (valoare 17) flagurile _O_CREAT şi _O_EXCL sunt
specificate, dar numele de fişier este al unui fişier deja existent.
EINVAL – (valoare 22) unul dintre argumentele oflag sau
pmode sunt invalide.
EMFILE – (valoare 24) nu mai sunt disponibile specificatoare
de fişier (prea multe fişiere deschise).
ENOENT – (valoare 2) fişierul sau calea nu au fost găsite.
Variabila globală errno păstrează codurile de eroare folosite de
funcţiile perror (print error) sau strerror (string error) pentru tratarea
erorilor. Constantele manifest pentru aceste variabile sunt declarate în
STDLIB.H după cum urmează:
extern int _doserrno;
extern int errno;
errno este setată de o eroare într-un apel de funcţie la nivel de
sistem (la nivelul inferior). Deoarece errno păstrează valoarea setată
de ultimul apel, această valoare se poate modifica la fiecare apel de
funcţie sistem. Din această cauză errno trebuie verificată imediat
înainte şi după un apel care poate s-o modifice. Toate valorile errno,
definite drept constante manifest în ERRNO.H, sunt compatibile
UNIX. Valorile valide pentru aplicaţiile Windows pe 32 de biţi sunt
un subset al acestor valori UNIX. Valorile specificate mai sus sunt
valabile pentru aplicaţii Windows pe 32 de biţi.
La o eroare, errno nu este setată în mod necesar la aceeaşi
valoare cu codul erorii de sistem. Numai pentru operaţii de I/O se
foloseşte _doserrno pentru a accesa codul erorilor sistemului de
operare echivalent cu codurile semnalate de errno. Exemplu:
Acest program foloseste _open pentru a deschide un fisier numit
OPEN.C pentru citire si un fisier numit OPEN.OUT scriere. Apoi
fisierele sunt inchise
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <io.h>

203
#include <stdio.h>
void main( void )
{
int fh1, fh2;
fh1 = _open( "OPEN.C", _O_RDONLY );
if( fh1 == -1 )
perror( "open failed on input file" );
else
{ printf( "open succeeded on input file\n" );
_close( fh1 );}
fh2=_open("OPEN.OUT",_O_WRONLY|_O_CREAT,_S_IREAD|_S_IWRITE);
if( fh2 == -1 )
perror( "Open failed on output file" );
else
{printf( "Open succeeded on output file\n" );
_close( fh2 );}}
Prin execuţia acestui program se vor obţine următoarele mesaje
pe display:
open failed on input file: No such file or directory
Open succeeded on output file
Press any key to continue

10.3.2. Scrierea într-un fişier


Scrierea într-un fişier se realizează folosind funcţia _write. Se
presupune că fişierul respectiv a fost în prealabil deschis prin funcţiile
_open sau _creat. Ea este asemănătoare cu funcţia _read, doar că se
realizează transferul de date în sens invers şi anume din memorie pe
suportul fiierului. Funcţia _write, ca şi _read, se apelează printr-o
atribuire de forma:
nr = _read(df,zt,n)
unde:
nr – este o variabilă de tip întreg căreia i se atribuie numărul de octeţi
scrişi în fişier.
df – este descriptorul de fişier returnat de funcţia _open la deschiderea
sau _creat la crearea fişierului.
zt - este un pointer spre zona tampon definită de utilizator, zonă din
care se face scrierea.
n – este dimensiunea zonei tampon sau numărul de octeţi care se
doreşte să se scrie.
Definiţia funcţiei este:
int _write( int handle, const void *buffer, unsigned int count );

204
Funcţia _write scrie count octeţi din buffer în fişierul asociat cu
descriptorul handle. Operaţia de scriere începe la poziţia curentă a
pointerului de fişier asociat cu fişierul dat. Dacă fişierul este deschis
cu indicatorul _O_APPEND, operaţia de scriere începe la sfârşitul
fişierului. După o operaţie de scriere pointerul de fişier este
incrementat cu numărul de biţi scrişi efectiv.
Dacă fişierul a fost deschis în mod text (implicit), atunci _write
tratează CTRL/Z drept un caracter ce indică sfârşitul logic al
fişierului. Când se scrie într-un dispozitiv, _write tratează CTRL/Z din
buffer drept terminator al operaţiei de ieşire.
În general trebuie ca la revenirea din funcţia _write să avem
nr=n, ceea ce semnifică faptul că s-au scris pe disc exact numărul de
biţi din buffer. În caz contrar scrierea este eronată: aceasta semnifică
faptul că pe disc a rămas mai puţin spaţiu (în octeţi) decât numărul de
octeţi ai bufferului. Dacă valoarea returnată este -1, se semnalizează
eşecul operaţiei de scriere. În acest caz variabila globală errno poate
avea una din valorile EBADF (care semnifică un descriptor de fişier
invalid sau că fişierul nu a fost deschis pentru scriere) sau ENOSPC
(care semnifică lipsa spaţiului pe disc pentru operaţia de scriere).
Funcţia _write poate fi utilizată pentru a scrie pe ieşirile standard
(display). Astfel, pentru a scrie pe ieşirea standard identificată prin
stdout se foloseşte descriptorul 1, iar pentru a scrie pe ieşirea standard
pentru erori, stderr, se foloseşte descriptorul de fişier 2. De asemenea,
în acest caz nu este nevoie să apelăm funcţia _open sau _creat
deoarece fişierele respective se deschid automat la lansarea
programului. Exemplu:
/*Acest program deschide un fisier pentru scriere si
foloseste _write pentru a scrie octeti in fisier*/
#include <io.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
char buffer[]="This is a test of '_write' function";
void main( void )
{ int fh;
unsigned byteswritten;
if((fh=_open("write.o",_O_RDWR|_O_CREAT,
_S_IREAD|_S_IWRITE))!=-1)
{
if((byteswritten = write(fh,buffer,sizeof(buffer)))== -1)

205
perror( "Write failed" );
else
printf( "Wrote %u bytes to file\n", byteswritten );
_close( fh );}}
În urma execuţiei programului, se va afişa mesajul:
Wrote 36 bytes to file
Press any key to continue

10.3.3. Citirea dintr-un fişier


Citirea dintr-un fişier deschis în prealabil cu funcţia _open se
realizează cu ajutorul funcţiei _read. Ea returnează numărul efectiv al
octeţilor citiţi din fişier. Funcţia _read se poate apela folosind o
expresie de atribuire de forma:
nr = _read(df,zt,n)
cu definiţia generală:
int _read( int handle, void *buffer, unsigned int count );
unde:
nr – este o variabilă de tip întreg căreia i se atribuie numărul de octeţi
citiţi din fişier.
df – este descriptorul de fişier returnat de funcţia open la deschiderea
sau creat la crearea fişierului.
zt - este un pointer spre zona tampon definită de utilizator, zonă în
care se face citirea.
n – reprezintă numărul de biţi care se citesc
Funcţia _read citeşte maximum count octeţi în buffer din fişierul
asociat cu descriptorul handle. Operaţia de citire începe de pe poziţia
curentă îndicată de pointerul de fişier asociat cu fişierul dat. După
operaţia de citire, pointerul de fişier indică spre următorul octet necitit
din fişier. Dacă fişierul a fost deschis în mod text, citirea se termină la
întâlnirea caracterului CTRL/Z, care este interpretat drept indicator de
sfârşit de fişier.
_read returnează numărul de biţi citiţi din fişier, care poate fi
mai mic decât count dacă sunt mai puţini decât count octeţi rămaşi în
fişier sau dacă fişierul este deschis în mod text. În acest caz fiecare
pereche CR-LF (carriage return–linefeed) (CR-LF) este înlocuită cu
un singur caracter LF. Numai acest caracter LF se consideră în
valoarea returnată. Înlocuirea nu afectează pointerul de fişier. Dacă
funcţia încearcă să citească după sfârşitul fişierului, se returnează
valoarea 0. Dacă descriptorul de fişier (handle) este invalid sau dacă

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

#include <fcntl.h> /* Necesara numai pentru definirea


_O_RDWR */
#include <io.h>
#include <stdlib.h>
#include <stdio.h>
char buffer[60000];
void main( void )
{ int fh;
unsigned int nbytes = 60000, bytesread;

/* Deschide fisierul in citire: */


if( (fh = _open( "write.o", _O_RDONLY )) == -1 )
{ perror( "open failed on input file" );
exit( 1 ); }
/* Read in input: */
if((bytesread = _read(fh,buffer,nbytes)) <= 0)
perror( "Problem reading file" );
else

207
printf( "Read %u bytes from file\n", bytesread );
_close( fh );}
La execuţia programului se va afişa următorul mesaj:
Read 36 bytes from file
Press any key to continue

10.3.4. Închiderea unui fişier


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

208
poziţiona oriunde în fişierul respectiv O astfel de poziţionare este
posibilă pe hard-uri şi floppy-uri prin funcţia _lseek.
Definiţia funcţiei este:
long _lseek( int handle, long offset, int origin );
Funcţia _lseek mută pointerul de fişier asociat cu descriptorul
handle (df) pe o nouă locaţie care este situată la offset octeţi de origin.
Următoarea operaţie de citire/scriere se va efectua de la noua locaţie.
Argumentul origin trebuie să fie una dintre următoarele constante,
definite în STDIO.H:
SEEK_SET – începutul fişierului (valoare 0)
SEEK_CUR – poziţia curentă a pointerului de fişier (valoare 1)
SEEK_END – sfârşitul fişierului (valoare implicită 2)
Funcţia _lseek returnează valoarea 0 la poziţionare corectă şi -1
la incident. Ea poate fi apelată prin:
v = _lseek(df, deplasament, origine)
unde:
v – este o variabilă de tip întreg căreia i se atribuie valoarea returnată
de către funcţie (0 sau -1)
df – este descriptorul de fişier (handle) a cărui valoare a fost definită la
deschiderea sau crearea fişierului.
deplasament – este o variabilă de tip long şi conţine numărul de octeţi
peste care se va deplasa capul de citire/scriere al discului.
origine – are una din valorile
0 - deplasamentul se socoteşte de la începutul fişierului;
1 - deplasamentul se socoteşte din poziţia curentă a capului de
citire/scriere;
2 - deplasamentul se socoteşte de la sfârşitul fişierului.
Menţionăm că prin apelul lui _lseek nu se realizează nici un fel
de transfer de informaţie ci numai poziţionarea în fişier. Operaţia
următoare realizată prin apelul funcţiei _read sau _write se va realiza
din această poziţie. Spre exemplu, apelul:
v = _lseek(df, 0l, 2)
permite să se facă poziţionarea la sfârşitul fişierului. În continuare se
pot adăuga articole folosind funcţia _write.
Poziţionarea la începutul fişierului se face prin apelul:
v = _lseek(df, 0l, 0)
Exemplu:
#include <io.h>
#include <fcntl.h>
#include <stdlib.h>

209
#include <stdio.h>
void main( void )
{ int fh;
long pos; /* Pozitia pointerului fisier */
char buffer[10];
fh = _open( "write.o", _O_RDONLY );
/* Pozitionare la inceputul fisierului: */
pos = _lseek( fh, 0L, SEEK_SET );
if( pos == -1L )
perror( "_lseek inceput nu a reusit!" );
else
printf("Pozitia pentru inceputul fisierului = %ld\n",
pos );
/* Muta pointerul fisier cu 10 octeti */
_read( fh, buffer, 10 );

/* Gaseste pozitia curenta: */


pos = _lseek( fh, 0L, SEEK_CUR );
if( pos == -1L )
perror( "_lseek pozitia curenta nu a reusit!" );
else
printf( "Pozitia curenta = %ld\n", pos );

/* Pozitionare pe ultima pozitie: */


pos = _lseek( fh, 0L, SEEK_END );
if( pos == -1L )
perror( "_lseek sfarsit nu a reusit!" );
else
printf( "Pozitia ultima este = %ld\n", pos );
_close( fh );}
În urma execuţiei programului se va afişa:
Pozitia pentru inceputul fisierului = 0
Pozitia curenta = 10
Pozitia ultima este = 36
Press any key to continue

10.3.6 Ştergerea unui fişier


Un fişier poate fi şters apelând funcţia _unlink astfel:
v = _unlink(spf)
unde v este o variabilă de tip întreg căreia i se atribuie valoarea 0
pentru ştergere reuşită şi (-1) pentru ştergere nereuşită.
spf este specificatorul de fişier folosit la deschidere a fişierului.
Definiţia funcţiei este:
int _unlink( const char *filename );
Funcţia _unlink şterge de pe disc fişierul specificat prin filename.
Exemplu:
210
/* Acest program sterge fisierul WRITE.O creat si prelucrat anterior. */
#include <stdio.h>
void main( void )
{ if( _unlink( "write.o" ) == -1 )
perror( "Nu se poate sterge 'WRITE.O'" );
else
printf( "S-a sters 'WRITE.O'\n" );}
În urma execuţiei programului se afişează:
S-a sters 'WRITE.O'
Press any key to continue

10.3.7. Exemple de utilizare a funcţiilor de intrare/ieşire de


nivel inferior
1. Să se scrie un program care copiază intrarea standard la
ieşirea standard.
Această problemă se poate rezolva uşor prin folosirea funcţiilor
getchar şi putchar. Acum o vom rezolva folosind funcţiile _read şi
_write.
# include <stdio.h>
# include <io.h>
void main() /* copiaza intrarea standard la iesirea
standard */
{ char c[1];
while (_read(0,c,1)>0)
_write(1,c,1);}
Menţionăm că cel de-al doilea parametru al funcţiei _read sau
_write trebuie să fie pointer spre caractere.
Lucrul la nivelul inferior nu este chiar atât de simplu pe cât
pare. Vom ilustra în continuare responsabilitatea pe care o are
programatorul în gestionarea zonelor tampon. Să considerăm
exemplul anterior în care zona tampon o mărim la 3 caractere, deci
programul arată astfel:
# include <stdio.h>
# include <io.h>
void main()
{ char c[3];
while (_read(0,c,3)>0)
_write(1,c,3);}
Citirea nu se va opri după 3 caractere, ci funcţia _read va
continua să funcţioneze până la tastarea ENTER (CR+LF). Imediat
funcţia _read va tipări grupele de 3 caractere introduse, inclusiv
grupul final CR+LF. Zona tampon definită este supraînscrisă de
fiecare dată când se introduc noi caractere.
211
Dacă de la tastatură vom introduce
123456<CR><LF>
atunci se va tipări primul grup (prima înscriere a zonei tampon) 123,
apoi a doua grupă 456 şi grupul <CR> şi <LF> va supraînscrie
primele două caractere ale bufferului, anume codurile ASCII ale lui 4
şi 5 şi se va tipări <CR><LF>6.
123456
123456
6
Primul grup 123456 este scris prin ecou de la tastatură, iar
următoru se înscrie de către program. Dacă în continuare vom
introduce 1<ENTER> atunci se va tipări 1 urmat de două rânduri noi
deoarece fiecare CR sau LF sunt expandate de stdout în perechi
<CR><LF>.
Dacă mărim la 5 dimensiunea bufferului şi de la tastatură
introducem 12<ENTER>, atunci se va tipări
12
12
¦
deoarece cel de-al 5-lea octet al bufferului nu a fost alocat prin citire,
având o valoare nedefinită.
Problemele de mai sus legate de gestiunea bufferului în/din care
se face citirea/scrierea pot fi depăşite cu o modificare simplă,
prezentată mai jos. Prin scriere nu se vor trimite spre stdout decât
numărul de caractere citit de la stdin.
# include <stdio.h>
# include <io.h>
# define LZT 10 // lungime zona tampon
void main() /* copiaza intrarea standard la iesirea
standard */
{ char zt[LZT];
int n;
while ((n=_read(0,zt,LZT))>0)
_write(1,zt,n);}
Programatorul trebuie să ţină cont însă şi de alte amănunte cum
ar fi dimensiunea implicită a bufferului stdin, care este de 254 de
caractere.
2. Să se scrie un program care citeşte un şir de numere flotante de la
intrarea standard şi crează 2 fişiere fis1.dat şi fis2.dat, primul
conţinând numerele de ordin impar citite de la intrarea standard
212
(primul, al 3-lea, al 5-lea, etc.) iar cel de-al doilea pe cele de ordin
par citite de la aceeaşi intrare. Apoi să se listeze, la ieşirea
standard, cele două fişiere în ordinea fis1.dat, fis2.dat câte un
număr pe un rând în formatul
număr de ordine: număr
Vom scrie programul folosindu-ne de funcţii definite de
utilizator care să facă apel la funcţiile de nivel inferior. Programul
arată astfel:
# include <stdio.h>
# include <io.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
#include <stdlib.h>
char nume1[]="fis1.dat";
char nume2[]="fis2.dat";
union unr {
float nr;
char tnr[sizeof(float)];};
union unr nrcit;
int creare_fis(const char *nume)
{ int df;
if ((df=_creat(nume,_S_IWRITE|_S_IREAD))==-1)
{ printf("%s: ",nume);
printf("Nu se poate deschide fisierul in creare\n");
exit(1);}
return df;}
void scrie_fis(int df,char *nume)
{if(_write(df,nrcit.tnr,sizeof(float))!=sizeof(float))
{ printf("%s: ",nume);
printf("Eroare la scrierea fisierului\n");exit(1);}}

void date_fis(int df1,char *nume1,int df2,char *nume2)


{ int j=1,i;
while ((i=scanf("%f",&nrcit.nr))==1) {
if(j%2) scrie_fis(df1,nume1);
else scrie_fis(df2,nume2);
j++;}}
void inchid_fis(int df, char *nume)
{ if (_close(df)<0) {
printf("%s: ",nume);
printf("eroare la inchiderea fisierului\n");
exit(1);}}
int deschid_fis_cit(char *nume)
{ int df;
if ((df=_open(nume,_O_RDONLY))==-1) {

213
printf("%s: ",nume);
printf("Nu se poate deschide fisierul in citire\n");
exit(1);}
return df;}
void list_fis(int df,char *nume,int ord)
{ int j,i;
if (ord%2) j=1; else j=2;
while ((i=_read(df,nrcit.tnr,sizeof(float)))>0) {
printf("%6d: %g\n",j,nrcit.nr);j+=2;}
if (i<0) {
printf("%s: ",nume);
printf("Eroare la citire din fisierul\n"); exit(1);}
_close(df);}
void main() {
int df1,df2;
df1=creare_fis(nume1);
df2=creare_fis(nume2);
date_fis(df1,nume1,df2,nume2);
inchid_fis(df1,nume1);inchid_fis(df2,nume2);
df1=deschid_fis_cit(nume1);
df2=deschid_fis_cit(nume2);
list_fis(df1,nume1,1);list_fis(df2,nume2,2);}

3. Să se realizeze programul de mai sus folosind un singur fişier


fis.dat.
Programul va diferi faţă de cel anterior prin faptul că
înregistrările se stochează într-un singur fişier, deci funcţia de listare
se va modifica pentru citirea din 2 în 2 a înregistrărilor. După fiecare
citire din fişier, se va face un salt cu o înregistrare pentru a poziţiona
capul de citire/scriere peste înregistrarea următoare.
# include <stdio.h>
# include <io.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
#include <stdlib.h>
char nume[]="fis.dat";
union unr {
float nr;
char tnr[sizeof(float)];};
union unr nrcit;
int creare_fis(const char *nume)
{ int df;
if ((df=_creat(nume,_S_IWRITE|_S_IREAD))==-1)
{ printf("%s: ",nume);
printf("Nu se poate deschide fisierul in creare\n");

214
exit(1);}
return df;}
void scrie_fis(int df,char *nume)
{ if (_write(df,nrcit.tnr,sizeof(float))!=sizeof(float))
{ printf("%s: ",nume);
printf("Eroare la scrierea fisierului\n");exit(1);}}
void date_fis(int df,char *nume)
{ while (scanf("%f",&nrcit.nr)==1) {
scrie_fis(df,nume);}}
void inchid_fis(int df, char *nume)
{ if (_close(df)<0) {
printf("%s: ",nume);
printf ("eroare la inchiderea fisierului\n");
exit(1);}}
int deschid_fis_cit(char *nume)
{ int df;
if ((df=_open(nume,_O_RDONLY))==-1) {
printf("%s: ",nume);
printf("Nu se poate deschide fisierul in citire\n");
exit(1);}
return df;}
void list_fis(int df,char *nume)
{ int j,i;
j=1;
while ((i=_read(df,nrcit.tnr,sizeof(float)))>0) {
printf("%6d: %g\n",j,nrcit.nr);
// avans peste o inregistrare
if(_lseek(df,(long)sizeof(float),1)==-1l)
break;
j+=2;}
if (i<0) {
printf("%s: ",nume);
printf("Eroare la citire din fisierul\n");
exit(1);}
j=2;
// pozitionare pe prima inregistrare
_lseek(df,0l,0);
// avans la inregistrarea a doua
_lseek(df,(long)sizeof(float),1);
while((i=_read(df,nrcit.tnr,sizeof(float)))>0)
{printf("%6d: %g\n",j,nrcit.nr);
// avans peste o inregistrare
if(_lseek(df,(long)sizeof(float),1)==-1l)
break; j+=2;}
if (i<0) { printf("%s: ",nume);
printf("Eroare la citire din fisierul\n");
exit(1);}
_close(df);}

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

216
Funcţia deschide fişierul al cărui nume este specificat prin
"filename" (de obicei un fişier disc) şi întoarce un pointer la FILE
pentru operaţie reuşită şi NULL pentru operaţie nereuşită.
Varibilele permise pentru modul "mode" sunt:
a _O_WRONLY | _O_APPEND (usual _O_WRONLY |
_O_CREAT | _O_APPEND)
a+ _O_RDWR | _O_APPEND (usual _O_RDWR | _O_APPEND |
_O_CREAT )
r _O_RDONLY
r+ _O_RDWR
w _O_WRONLY(usual _O_WRONLY | _O_CREAT |
_O_TRUNC)
w+ _O_RDWR (usual _O_RDWR | _O_CREAT | _O_TRUNC)

b _O_BINARY
t _O_TEXT
c Nimic
n Nimic

Modul "a" nu şterge markerul de sfârşit d fişier EOF înainte de


a adăuga la sfârşitul fişierului. După ce s-a făcut o adăugare, comanda
MS-DOS TYPE tipăreşte datele până la markerul original EOF şi nu
până la ultima dată adăugată. Modul "a+" şterge identificatorul de
sfârşit de fişier EOF înainte de adăugarea de înregistrări la sfârşitul
fişierului. După adăugare comanda MS-DOS TYPE va tipări toate
datele conţinute în fiier. Modul "a+" este cerut pentru adăugarea la
sfârşitul unui fişier care are marker terminator CTRL/Z = EOF.
Dacă modul "mode" include "b" după litera iniţială, ca în "rb"
sau "w+b" se indică un fişier binar. Numele fişierului conţine cel mult
FILENAME_MAX caractere. La un moment dat pot fi deschise cel
mult FOPEN_MAX fişiere.
Menţionăm că stdin, stdout şi stderr sunt pointeri spre tipul
FILE şi permit ca funcţiile de nivel superior de prelucrare a fişierelor
să poată trata intrarea standard, ieşirea standard şi ieşirea standard
pentru erori, la fel ca şi restul fişierelor. Singura deosebire constă în

217
faptul că în acest caz programatorul nu trebuie să deschidă sau să
închidă fişierele respective. Exemplu:
FILE *fp, *fopen(); /* se declara pointerii de tip file *fp si *fopen() */
fp = fopen("test","w"); /* se deschide fisierul " test " pentru screiere */
Pentru detectarea unei erori la deschiderea unui fişier se utilizează
secvenţa:
if ((fp = fopen("test", "w")) == NULL) {
puts ("Cannot open file\n");
exit(1); }
Dacă pentru operaţia de citire se încearcă deschiderea unui fişier
inexistent, fopen() va returna o eroare.
10.4.2. Funcţia fclose()
Forma generală de declarare a funcţiei fclose() este:
int fclose(FILE *fp);
unde "fp" este pointerul la fişier returnat după apelul funcţiei fopen().
Funcţia fclose() se utilizează pentru a închide un fişier deschis cu
fopen(). Funcţia fclose() scrie mai întâi în fişier datele rămase în
fişierul buffer apoi închide fişierul. fclose() întoarce zero (0) pentru
închidere reuşită şi EOF (-1) dacă apare o eroare.
La execuţia funcţiei exit se închid automat toate fişierele
deschise. Cu toate acestea, se recomandă ca programatorul să închidă
un fişier de îndată ce s-a terminat prelucrarea lui, altfel se poate ajunge
în situaţia de a se depăşi numărul limită admis pentru fişierele care pot
fi simultan deschise într-un program.
Exemplu:
/* Acest program deschide fisierele numite "test1.c"
si "test2.c".Apoi foloseste fclose pentru a inchide "test1.c" si _fcloseall
pentru a inchide restul fisierelor deschise */
#include <stdio.h>
FILE *stream1, *stream2;
void main( void ){
int numclosed;
/* Deschidere in citire (esec daca fisierul "test1.c" nu
exista) */
if( (stream1 = fopen( "test1.c", "r" )) == NULL )
printf( "Fisierul 'test1.c' nu a fost deschis\n" );
else
printf( "Fisierul 'test1.c' a fost deschis\n" );
/* Deschidere pentru scriere */
if( (stream2 = fopen( "test2.c", "w+" )) == NULL )
printf( "Fisierul 'test2.c' nu a fost deschis\n" );
else

218
printf( "Fisierul 'test2.c' a fost deschis\n" );
/* Inchide fisierul cu pointerul stream1 */
if( fclose( stream1 ) )
printf( "Fisierul 'test1.c' nu a fost inchis\n" );
/* Toate celelalte fisiere se inchid: */
numclosed = _fcloseall( );
printf( "Numarul fisierelor inchise cu _fcloseall: %u\n",
numclosed );}
În urma execuţiei programului se obţine:
Fisierul 'test1.c' a fost deschis
Fisierul 'test2.c' a fost deschis
Numarul fisierelor inchise cu _fcloseall: 1
Press any key to continue

10.4.3. Funcţiile rename() şi remove()


Funcţia rename() schimbă numele vechi al fişierului,
"oldname", cu numele nou, "newname". Întoarce o valoare diferită de
zero dacă incercarea nu reuseste.
int rename (char *oldname, char *newname);
Funcţia remove()
int remove(char *filename);
Funcţia remove() elimină fişierul cu numele specificat, astfel
încât o incercare ulterioară de deschidere a fişierului va eşua. Întoarce
o valoare diferită de zero dacă încercarea reuşeşte.
10.4.4. Funcţii de tratare a erorilor
a) Funcţia ferror()
Aceasta funcţie determină dacă în urma unei operaţii cu fişiere s-
a produs sau nu o eroare.
Forma generală de declarare este:
int ferror(FILE *fp)
unde "fp" este un pointer la fişier.
Funcţia ferror() întoarce o valoare diferită de zero dacă s-a
detectat o eroare şi o valoare 0 dacă nu s-a detectat nici o eroare.
b) Funcţia feof()
int feof(FILE *fp)
Funcţia feof() întoarce o valoare diferită de zero dacă indicatorul
de sfârşit de fişier este valid şi o valoare zero dacă indicatorul de
sfârşit de fişier nu este valid.
c) Funcţia perror()
void perror(const char *s)

219
Funcţia perror() scrie s şi un mesaj de eroare care depinde de
implementare, corespunzator cu intregul din errno.h, astfel:
fprintf(stderr,"%s %s\n, s, "error message")

10.4.5. Funcţii cu acces direct


a) Funcţia fread()
Permite citirea unui bloc de date. Forma generală de declarare:
int fread(void *buffer,int num_bytes,int count,FILE *fp)
Funcţia fread() citeşte din fişierul specificat prin "fp" cel mult
"count" obiecte, fiecare obiect având lungimea egală cu "num_bytes"
şi îi trimite în zona de memorie indirectată prin "buffer" .
*fp este un pointer fişier la fişierul deschis anterior cu fopen().
Funcţia întoarce numărul de obiecte citite, acesta putând fi mai
mic decât cele cerute. Pentru a determina starea funcţiei se pot utiliza
funcţiile feof(), ferror().
b) Funcţia fwrite()
Permite scrierea unui bloc de date. Forma generală de declarare:
int fwrite(void *buffer,int num_bytes,int count, FILE *fp)
Funcţia fwrite() scrie din zona (tabloul) "buffer" în fişierul
indirectat prin "fp", "count" obiect de lungime "nr_bytes". Funcţia
întoarce numărul de obiecte scrise, care, în caz de eroare este mai mic
decât "count".
Exemplu: Programul următor scrise un număr real pe disc
# include "stdio.h"
void main() {
FILE *fp;
float f = 12.23;
if ((fp = fopen ("test", "wb")) == NULL){
printf (" Cannot open file\n ");
return; }
fwrite (&f, sizeof (float), 1, fp);
fclose (fp); }
Aşa cum se vede din acest program, "buffer" poate fi o simplă
variabilă.
Exemplu: Programul următor copiază un tablou de numere reale
"balance", în fişierul "balance":
# include "stdio.h"
void main()
{ FILE *fp;
float balance[100]; /* tabloul balance */
if ((fp = fopen("balance", "w+")) == NULL) {
printf ("Cannot open file\n");
return;}
220
. . . . . . . . . . . . . . . . .
fwrite (balance, sizeof (balance), 1, fp);
. . . . . . . . . . . . . . . . .
fclose (fp); }
Exemplu: Programul următor deschide fişierul FREAD.OUT şi scrie
în el 25 de caractere şi apoi îl redeschide şi citeşte din nou caracterele
din fişier după care afişează numărul caracterelor citite şi conţinutul.
#include <stdio.h>
void main( void )
{ FILE *stream;
char list[30];
int i, numread, numwritten;
/* Deschide fisierul in mod text: */
if( (stream = fopen( "fread.out", "w+t" )) != NULL )
{ for ( i = 0; i < 25; i++ )
list[i] = (char)('z' - i);
/* Scrie 25 caractere in fisier */
numwritten = fwrite(list,sizeof(char),25,stream );
printf( "S-au scris %d caractere\n", numwritten );
fclose( stream );}
else
printf( "Probleme cu deschiderea fisierului\n" );
if( (stream = fopen( "fread.out", "r+t" )) != NULL )
{ /* Incearca sa citeasca 25 caractere */
numread = fread( list, sizeof( char ), 25, stream );
printf("Nr. caracterelor citite = %d\n", numread);
printf( "Continutul bufferului = %.25s\n", list );
fclose( stream );}
else
printf( "Fisierul nu a putut fi deschis\n" );}
În urma execuţie programului se obţine:
S-au scris 25 caractere
Nr. caracterelor citite = 25
Continutul bufferului = zyxwvutsrqponmlkjihgfedcb
Press any key to continue

10.4.6. Funcţii pentru poziţionare


a) Funcţia fseek()
Determină poziţionarea fişierului la citire sau scriere, începând
cu poziţia selectată. Forma funcţiei:
int fseek(FILE *fp, long offset, int origin)
unde "fp" este un pointer-fişier returnat prin apelul funcţiei fopen(),
"offset" este deplasamentul (număr octeţi) noii poziţii faţă de "origin",
iar "origin" este una din următoarele macrodefiniţii:
SEEK_SET - început de fişier;
221
SEEK_CUR - poziţie curentă;
SEEK_END - sfârşit de fişier.
Funcţia returnează 0 dacă se execută cu succes şi o valoare nenulă în
caz de eroare.
Dacă nu s-a efectuat nici o operaţie de I/O de la deschiderea
fişierului în mod APPEND (adăugare), atunci pointerul indică
începutul fişierului.
Nu se recomanda utilizarea funcţiei fseek() pentru fişiere text; se
sugerează utilizarea acesteia numai pentru fişiere binare. Translaţiile
CR-LF efectuate în mod text pot cauza funcţionarea defectoasă a
funcţiei fseek. Funcţia fopen şi toate celelalte funcţii vor căuta să
înlăture caracterul CTRL/Z terminator de fişier (EOF).
Singurele operaţii garantate să funcţioneze corect când se
utilizează fseek asupra fişierelor deschise în mod text este poziţionarea
cu offset 0 relativă la orice poziţie din fişier şi poziţionarea faţă de
începutul fişierului cu un offset returnat de funcţia ftell().
Funcţia ftell() este definită astfel:
long ftell( FILE *stream );
Funcţia returnează valoarea curentă a pointerului fişier. Poziţia
este exprimată prin offsetul faţă de începutul fiierului.
În cazul fişierelor deschise în mod text, acest offset nu reflectă
întotdeauna exact numărul de octeţi datorită translaţiei CR-LF. Este
preferată folosirea simultană a funcţiilor fseek şi ftell pentru a opera
asupra fişierelor text, dar se recomandă folosirea lor în special asupra
fişierelor binare.
Exemplu: Pentru a citi cel de-al 235 byte din fişierul numit "test" se
poate folosi următorul program:
func1() /* se declara funcţia func1() */
{ FILE *fp;
if ((fp = fopen("test", "rb")) == NULL) {
printf ("Cannot open file\n"); exit (1); }
fseek(fp, 235L, 0);
return getc(fp);} /* se citeste un caracter de la pozitia
235 */
Observaţie: L modifică constanta 235 la tipul long int.
Exemplu:
/* Acest program deschide fisierul FSEEK.OUT si muta
pointerul in diverse locuri din fisier */
#include <stdio.h>
void main( void )
{ FILE *stream;
char line[81];
222
int result;
stream = fopen( "fseek.out", "w+" );
if( stream == NULL )
printf( "”Fisierul fseek.out nu s-a deschis\n" );
else
{fprintf( stream, "Fseek incepe aici: "
"Acesta este fisierul 'fseek.out'.\n" );
result = fseek( stream, 19L, SEEK_SET);
if( result ) perror( "Fseek esec" );
else
{ printf( "Pointerul fisier este plasat la mijlocul
primei linii.\n" );
fgets( line, 80, stream );
printf( "%s", line );}
fclose( stream );}}
În urma execuţie programului se obţine:
Pointerul fisier este plasat la mijlocul primei linii.
Acesta este fisierul 'fseek.out'.
Press any key to continue

10.4.7. Ieşiri cu format


Funcţiile de tip printf() asigură conversiile de ieşire cu format.
a) Funcţia fprintf()
Forma acestei funcţii este:
int fprintf(FILE *fp, "format", lista_argumente)
Funcţia fprintf() realizează conversia şi scrierea la ieşire în
fişierul indirectat cu "fp" sub controlul formatului, "format". Valoarea
întoarsă de funcţie este numărul de caractere scrise, sau orice valoare
negativă, dacă apare o eroare.
Şirul "format" conţine două tipuri de obiecte: caractere obişnuite
care sunt copiate în fişierul de ieşire şi descriptori de conversie,
fiecare determinând conversia şi tipărirea argumentelor din lista de
argumente. Fiecare descriptor începe cu caracterul % şi se încheie cu
un caracter de conversie. Între % şi caracterul de conversie pot exista:
1) Indicatori (în orice ordine):
"-" - determină alinierea la stânga a argumentului convertit în
câmpul de reprezentare;
"+" - precizează că numărul va fi reprezentat cu semn;
" " - dacă primul caracter nu este un semn se va scrie un blanc la
început;
"0" - se utilizează în conversiile numerice şi indică umplerea cu
zerouri la începutul câmpului;

223
"#" - indică o formă de ieşire alternativă : pentru "0", prima cifra
va fi zero; pentru "x" sau "X", la începutul fiecărui număr nenul se va
scrie "0x" sau "0X"; pentru "e", "E", "g", "G", "f" ieşirea va avea
întotdeauna un punct zecimal; pentru "g" şi "G" nu se vor elimina
zerourile de la sfârşit.
2) Un număr care indică lungimea minimă a câmpului de
reprezentare.
Argumentul convertit va fi tipărit într-un câmp cu o lungime cel
puţin egală cu cea specificată, dacă va fi nevoie şi mai mare. Dacă
argumentul specificat are mai puţine caractere, atunci câmpul va fi
umplut la stânga sau la dreapta, funcţie de aliniere. Caracterul de
umplere este de obicei spatiul, dar este 0 dacă s-a ales această optiune
(exemplu: %05d).
3) Un punct ce separă lungimea câmpului de precizie.
4) Un număr, precizia, care indică numărul maxim de caracetre care
se vor tipări după virgulă pentru "e", "E", sau "f", sau numărul de cifre
semnificative pentru conversiile "g" sau "G", sau numărul maxim de
caractere ce se vor tipări dintr-un şir. Lungimea, sau precizia, sau
amândoua se pot specifica şi prin "*". De exemplu:
%10.4f - va afişa un număr de cel puţin 10 caractere cu 4
caractere după virgulă;
%5.7s - va afişa un şir de cel puţin 5 caractere dar nu mai lung
de 7 caractere;
%-10.2f - va alinia la stânga un număr real cu 2 zecimale într-
un câmp de reprezentare de cel puţin 10 caractere.
Descriptorii de conversie utilizaţi de C sunt:
%c - un singur caracter.
%d, %i - notaţie zecimala cu semn.
%x, %X - notaţie hexazecimală fără semn (fără 0x sau 0X).
%u - notaţie zecimală fără semn.
%s - caracterele din şir sunt tipărite până se întâlneşte
'\0' sau cât timp numărul de caractere tipărit precizia.
%f - notaţie zecimală de forma [-]mmm.ddd, unde
numărul d-urilor este indicat de precizie; precizia implicită este 6, iar
o precizie egală cu zero elimina punctul zecimal.
%e, %E - notaţie zecimală de forma:
[-]m.dddddde+/-xx sau
[-]m.ddddddE+/-XX

224
unde numărul de d-uri este indicat de precizie
(precizia implicită este 6, iar o precizie egală cu 0
va elimina punctul zecimal).
%g, %G - se utilizează %e sau %E dacă exponentul este mai
mic decât -4, sau precizie, în caz contrar se
utilizează %f.
%p - afiseaza un pointer.
%o - notaţie octalăa fără semn (fără 0 la început).
%% - nu se face conversie, se tipăreşte "%".
%n - nu se realizează conversie de argument; numărul de
caractere scrise până în acel moment este scris în argument.
Există doi modificatori de format care permit funcţiei fprintf() să
afişeze întregii long şi short. Aceşti modificatori se pot aplică
caracterelor de conversie d, i, o, u şi x, precedându-i pe aceştia
(exemplu: %ld, %li, %lu). Modificatorul l poate prefixa şi caracterele
de conversie e, f şi g şi indică faptul că numerele tiparite sunt de tip
double. Modificatorul h comandă funcţiei fprintf() să afişeze short int.
Atunci %hu va preciza că data este de tip short unsigned int.]
b) Funcţia printf()
Forma funcţiei :
int printf("format", lista-argumente)
Funcţia printf() este echivalentă cu :
fprintf(stdout, "format", lista_argumente)
Exemplu:
printf() ieşire
("%-5.2f", 123.456) 123.45
("%5.2f", 3.4565) 3.45
("%10s", "hello") hello
("%-10s", "hello") hello
(%5.7s", "123456789") 1234567
Exemplu de utilizare a functiei fprintf.
/* Acest program foloseste fprintf pentru scrierea datelor cu diferite
formate intr-un fisier si apoi tipareste fisierul folosind functia sistem
system ce apeleaza comanda TYPE a sistemului de operare */
#include <stdio.h>
#include <process.h>
FILE *stream;
void main( void )
{ int i = 10;
double fp = 1.5;
char s[] = "this is a string";

225
char c = '\n';
stream = fopen( "fprintf.out", "w" );
fprintf( stream, "%s%c", s, c );
fprintf( stream, "%d\n", i );
fprintf( stream, "%f\n", fp );
fclose( stream );
system( "type fprintf.out" );}

10.4.8. Intrări cu format


Funcţiile de tip scanf() realizează conversiile de intrare cu format
a) Funcţia fscanf()
Forma acestei funcţii este:
int fscanf(FILE *fp, "format", lista_argumente)
Funcţia fscanf() citeşte din fişierul indirectat prin "fp" sub
controlul formatului "format" şi atribuie valorile citite argumentelor
următoare, fiecare argument trebuind să fie un pointer.
Funcţia întoarce EOF dacă se detectează sfârşitul de fişier sau
apare o altă eroare înainte de orice conversie. În caz contrar, funcţia
întoarce numărul de elemente care au primit valori.
Şirul "format" poate conţine:
- specificatori de conversie, constând dintr-un caracter %, un
caracter opţional de suprimare a atribuirii; un număr opţional care
indică lungimea câmpului, un caracter opţional h, l sau L, care indică
lungimea argumentului şi un caracter de conversie;
- spaţii sau caractere HT sau VT care se ignoră;
- caractere obişnuite (diferite de %) care indică următorul
caracter diferit de caracterele albe cu care începe fişierul de intrare.
Un câmp de intrare se defineşte ca un şir de caractere diferite de
cele albe şi se întinde până la următorul caracter alb (spaţiu, tab-uri,
CR, LF, VT, FF).
Rezultatul conversiei unui câmp de intrare este plasat în
variabilă indicată de argumentul corespunzător din lista de argumente.
Dacă se indică suprimarea atributului prin "*" ca în %*s, câmpul
de intrare este ignorat, fără a se face nici o atribuire.
Descriptorii de conversie utilizaţi în C pentru citire sunt:
%c - citeşte un singur caracter; caracterele următoare sunt
plasate în tablourile indicate, respectându-se numărul de caractere
indicat de lungimea câmpului; implicit este 1. Nu se adaugă '\0'.
%d - citeşte un număr întreg zecimal.
%u - citeşte un număr întreg zecimal fără semn.

226
%i - citeşte un număr întreg (intregul poate fi octal, cu 0 la
început, sau hexazecimal, cu 0x sau 0X la început).
%o - întreg octal (cu sau fără zero la început).
%x - întreg hexazecimal (cu sau fără 0x sau 0X la început).
%s - şir de caractere diferite de caracterele albe, indicând
spre un tablou de caractere destul de mare pentru a memora şirul şi
caracterele terminator '\0' care se va adauga.
%e, %f, %g - numere zecimale în virgulă mobilă.
%p - citeşte valoarea pointerului.
%n - se scrie în argument numerele de caractere citite până în
acel moment. Nu se citeşte nimic din intrare.
%h - citeşte un întreg scurt.
Un caracter obişnuit în şirul "format" determină ca funcţia
fscanf() să citească un caracter ce coincide cu cele din "format". De
exemplu, "%d, %d" face că fscanf() să citească un întreg, apoi
caracterul "," şi apoi un alt întreg. Dacă calculatorul nu găseşte
caracterul specificat, fscanf() va fi încheiată.
Toate variabilele menite să primească valori prin fscanf() trebuie
să fie transmise prin adresele lor. Aceasta înseamnă că toate
argumentele trebuie să fie pointeri la variabilele utilizate ca
argumente.
b) Funcţia scanf()
Forma funcţiei:
int scanf("format", lista-argumente)
Funcţia scanf() este echivalenta cu:
fscanf(stdin, "format", lista-argumente)
Exemple:
scanf ("%d", &count); /* se citeşte un întreg în
variabilă count */
scanf ("%s", address); /* se citeşte un şir de caractere
în vectorul address */
scanf ("%d %d", &r, &c); /* se citesc doua numere
separate prin spaţiu, tab sau linie noua */

Un * plasat între % şi caracterul de conversie, va suspenda


atribuirea datei citite. Astfel, instrucţiunea :
scanf("%d%*c%d", &x, &y);
face ca, dacă de la tastatură se introduce 10/20, 10 să fie atribuit lui x,
%*c este ignorat (nu este atribuit), iar 20 se atribuie lui y.
Instrucţiunea :
scanf("%20s", sir);

227
citeşte nu mai mult de 20 caractere în variabilă şir. Dacă se introduce
un şir de mai mult de 20 caractere, vor fi reţinute numai primele 20,
iar restul se pierd. Pentru caracterele rămase se poate apela din nou
funcţia scanf() sub forma :
scanf("%s", sir);
care va plasa restul caracterelor tot în "şir".
Dacă de la tastatura se introduce 10#20, instrucţiunea :
scanf("%s#%s", &x, &y);
va plasa 10 în x şi 20 în y.
Instrucţiunea :
scanf("%s ", name);
nu se încheie decât dacă după ultimul caracter se introduce un spaţiu.
Exemplu de utilizare a funcţiilor fscanf şi fprintf.
/* Acest program scrie date cu format cu printf intr-un fisier. Apoi
foloseste fscanf pentru a citi datele din fisier */
#include <stdio.h>
FILE *stream;
void main( void )
{ long l;
float fp;
char s[81];
char c;
stream = fopen( "fscanf.out", "w+" );
if( stream == NULL )
printf( "Fisierul fscanf.out nu a fost deschis\n" );
else {
fprintf( stream, "%s %ld %f%c", "a-string",
65000, 3.14159, 'x' );
/* Seteaza pointerul la inceputul fisierului: */
fseek( stream, 0L, SEEK_SET );
/* Citeste datele inapoi din fisierul disc: */
fscanf( stream, "%s", s );
fscanf( stream, "%ld", &l );
fscanf( stream, "%f", &fp );
fscanf( stream, "%c", &c );
/* Tipareste datele citite din fisier: */
printf( "%s\n", s );
printf( "%ld\n", l );
printf( "%f\n", fp );
printf( "%c\n", c );
fclose( stream ); }}

10.4.9. Funcţii de citire şi scriere a caracterelor


a) Funcţia fgetc()
int fgetc(FILE *fp)

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

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

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

230
p = strptr1;
while((*p != '\0') && fputc(*(p++), stdout)!=EOF);
/* Identic cu _fputchar. (Aceasta functie nu este
compatibila ANSI */
p = strptr2;
while((*p != '\0') && _fputchar(*(p++))!=EOF);}
În general, funcţiile care încep cu _ (subliniere) dar şi cu f (de la
file) sunt destinate lucrului cu interfeţele standard stdin şi stdout.
b') Funcţia putc()
int putc(int ch, FILE *fp)
Funcţia putc() este echivalenta cu fputc() cu excepţia că este o
macrodefinitie, putând evalua fişierul mai mult decât o dată.
c') Funcţia putchar()
int putchar(int ch)
Funcţia putchar(ch) este echivalenta cu putc (ch, stdout).
d') Funcţia de la punctul d) nu are un corespondent pentru ieşire.
e') Funcţia puts()
int puts(const char *s)
Funcţia puts() scrie şirul "s" şi NL în "stdout". Întoarce EOF,
dacă apare o eroare, sau o valoare nenegativă în caz contrar.
f') Funcţia fputs()
int fputs(const char *s,FILE *fp)
Funcţia fputs() scrie şirul "s" (care nu este neapărat necesar să
conţină '\n') în fişierul "fp". Întoarce EOF, în caz de eroare, o valoare
nenegativă, în caz contrar. Spre exemplu,
/* Acest program foloseste fputs pentru a scrie o linie la terminalul
standard */
#include <stdio.h>
void main( void )
{ fputs( "Hello world from fputs.\n", stdout );}

Funcţia ungetc()
int ungetc(int ch,FILE *fp)
Funcţia ungetc() pune argumentul "ch" inpoi în fişier, de unde
va fi preluat la următoarea citire. Se poate pune inapoi în fişier doar un
singur caracter. Marcajul EOF nu poate fi pus înapoi. Funcţia întoarce
caracterul ce trebuie pus, sau EOF, în caz de eroare.
Funcţiile getw() şi putw()
Aceste funcţii se folosesc pentru a citi, respectiv a scrie întregi
dintr-un sau într-un fişier disc. Aceste funcţii lucrează exact că
funcţiile getc() şi putc().
Exemplu de utilizare a functiilor fprintf() şi fscanf() :
231
Programul permite actualizarea unei agende telefonice.
# include "stdio.h"
void ad_num(void); /*prototipul functiilor */
void cauta(void);
char menu(void);
void main() {
char choice;
do { choice = menu();
switch (choice) {
case 'a' : ad_num(); break;
case 'c' : cauta(); break; }
} while (choice != 'q'); }
char menu() {/* Afiseaza meniul si preia optiunea */
char ch;
do { printf ("A(dauga), C(auta), Q(uit) : ");
ch = tolower (getche());
printf ("\n");
} while (ch != 'q' && ch != 'a' && ch != 'c');
return ch; }
void ad_num() /* Adauga un nou numar */
{FILE *fp;
char name[80];
int a_code, schimb, numar;
if ((fp = fopen ("telefon", "a")) == NULL) {
printf ("Cannot open file \n"); exit (1);}
printf("Introduceti numele si numarul: ");
fscanf(stdin,"%s%d%d%d",nume,&a_code,&schimb, &numar);
fscanf(stdin,"%*c"); /* inlatura CR din sirul de
intrare */
/* Se scrie in fisier */
printf("%d",fprintf(fp,"%s %d %d %d\n", nume, a_cod,
schimb, numar));
fclose (fp); }
void cauta() /* Cauta un numar dandu-se un nume */
{ FILE *fp;
char nume[80], nume2[80];
int a_code, schimb, numar;
/* Se deschide fisierul pentru citire */
if ((fp = fopen ("telefon", "r")) == NULL) {
printf("Cannot open file\n "); exit (1); }
printf ("Nume ?"); gets (nume);
/* Se cauta numarul */
while (!feof (fp)) {
fscanf(fp,"%s%d%d%d", nume2, &a_cod, &schimb, &numar);
if (!strcmp(nume, nume2)) { printf("%s : (%d) %d -
%d\n", nume, a_code, schimb, numar);
break;} }
fclose (fp);}

232
Capitolul XI

UTILIZAREA ECRANULUI
ÎN MOD TEXT

Biblioteca standard a limbajului C şi C++ conţine funcţii pentru


gestiunea ecranului. Acesta poate fi gestionat în două moduri: în mod
text sau în mod grafic.
Prezentăm funcţiile standard mai importante utilizate la
gestiunea ecranului în mod text. Toate funcţiile standard de gestiune a
ecranului în mod text au prototipurile în fişierul antet <conio.h>.
Modul text presupune că ecranul este compus dintr-un număr de
linii şi un număr de coloane. În mod curent se utilizează 25 de linii a
80 de coloane fiecare, deci ecranul are o capacitate de 25*80=2000 de
caractere.
Poziţia pe ecran a unui caracter se defineşte printr-un sistem de
două coordonate întregi (x,y) unde:
x este numărul coloanei în care este situat caracterul
y este numărul liniei în care este situat caracterul
Colţul din stânga sus al ecranului are coordonatele (1,1), iar
colţul din dreapta jos are coordonatele (80,25).
În mod implicit, funcţiile de gestiune a ecranului în mod text au
acces la tot ecranul. Accesul poate fi însă limitat la o parte din ecran
utilizând aşa numitele ferestre. Fereastra este o parte a ecranului în
formă de dreptunghi şi care poate fi gestionată separat de restul
ecranului.
Atributele unui caracter de pe ecran sunt următoarele:
- coordonatele x şi y;
- culoarea caracterului;
- culoarea fondului pe care este afişat caracterul;
- clipirea caracterului.
11.1. Setarea ecranului în mod text
Se realizează cu ajutorul funcţiei textmode care are următorul
prototip:
void textmode (int modtext)

233
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.
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ă.

234
Funcţia clrscr poziţionează cursorul pe caracterul din stânga sus
al ferestrei active, adică în poziţia de coordonate (1,1).
11.4. Deplasarea cursorului
Cursorul poate fi plasat pe un caracter al ferestrei active
folosind funcţia gotoxy ce are următorul prototip:
void gotoxy (int x, int y);
unde (x, y) reprezintă coordonatele caracterului pe care se
plasează cursorul. Dacă la apelul funcţiei coordonatele sunt definite în
afara ferestrei active, funcţia este ignorată.
Poziţia cursorului din fereastra activă poate fi determinată cu
ajutorul funcţiilor wherex şi wherey care returnează numărul
coloanei, respectiv liniei, din fereastra activă pe care se află cursorul,
şi care au următoarele prototipuri:
int wherex(void);
int wherey(void);
În cazul în care se doreşte ascunderea cursorului (facerea
invizibilă a cursorului) se utilizează o secvenţă ce utilizează funcţia
geninterrupt:
{
_AH=1;
_CH=0x20;
geninterrupt(0x10); }
Cursorul poate fi rafiaşat utilizând următoarea secvenţă:
{ _AH=1;
_CH=6;
_CL=7;
geninterrupt(0x10);}

11.5. Setarea culorilor


Culoarea fondului se setează cu ajutorul funcţiei
textbackground ce are următorul prototip:
void textbackground(int culoare);
unde culoare este un întreg în intervalul [0, 7] şi are
semnificaţia din tabelul de mai sus.
Pot fi setate ambele culori precum şi clipirea caracterului
folosind funcţia textattr ce are următorul prototip:
void textattr (int atribut)
unde atribut se defineşte cu ajutorul relaţiei:
atribut=16*culoare_fond+culoare_caracter+clipire;

235
Culoarea caracterelor se setează cu ajutorul funcţiei textcolor
ce are următorul prototip:
void textcolor(int culoare);
unde culoare este un întreg în intervalul [0,15] a cărui
semnificaţie este explicată de tabelul următor:
Culoare Constantă simbolică Valoare
Negru BLACK 0
Albastru BLUE 1
Verde GREEN 2
Turcoaz CYAN 3
Roşu RED 4
Purpuriu MANGETA 5
Maro BROWN 6
Gri deschis LIGHTGRAY 7
Gri închis DARKGRAY 8
Albastru deschis LIGHTBLUE 9
Verde deschis LIGHTGREEN 10
Turcoaz deschis LIGHTCYAN 11
Roşu deschis LIGHTRED 12
Purpuriu deschis LIGHTMANGETA 13
Galben YELLOW 14
Alb WHITE 15
Clipire BLINK 128

11.6. Funcţii pentru gestiunea textelor


Pentru afişarea caracterelor se pot folosi funcţiile:
- int putch (int c); – afişează un singur caracter;
- int cputs (const char *str); – afişează un şir de caractere în
mod similar funcţiei puts;
- int cprintf (const char *format); – afişează date sub controlul
formatelor în mod similar funcţiei printf.
- void clreol (void); - şterge sfârşitul liniei începând cu poziţia
cursorului;
- void delline (void); - şterge toată linia pe care este poziţionat
cursorul;
- int gettext (int left, int top, int right, int bottom, void
*destination); - copiază textul cuprins în dreptunghiul definit de
coordonatele (left, top) – stânga sus şi (right, bottom) – dreapta jos la
adresa de memorie indicată de pointerul destination;
236
- int puttext( int left, int top, int right, int bottom, void *source
); - citeşte textul cuprins în dreptunghiul definit de coordonatele (left,
top) – stânga sus şi (right, bottom) – dreapta jos de la adresa de
memorie indicată de pointerul source;
- int movetext( int left, int top, int right, int bottom, int
destleft, int desttop ); - mută textul cuprins în dreptunghiul definit de
coordonatele (left, top) – stânga sus şi (right, bottom) – dreapta jos în
dreptunghiul cu coordonatele colţului din stânga sus (destleft,
desttop);
- void insline (void); - inserează o linie vidă în fereastra activă;
- int getch (void); - citeşte un caracter fără ecou de la tastatură,
adică după ce este citit caracterul nu mai este afişat pe ecran; funcţia
returnează codul ASCII al caracterului citit de la tastatură.
- int getche (void); - citeşte un caracter cu ecou de la tastatură,
adică după ce este citit caracterul este afişat automat pe ecran; funcţia
returnează codul ASCII al caracterului citit de la tastatură.
- int kbhit (void); - controlează dacă s-a tastat ceva la tastatură.
Dacă a fost apăsată o tastă se returnează o valoare diferită de zero,
altfel se returnează valoarea 0.
Exemplu: Următorul program desenează o fereastră şi scrie un număr
în aceasta.
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <alloc.h>
#include <dos.h>
#define MAX 100
#define SIMPLU 1
#define DUBLU 2
typedef struct{
int x,y,u,v;
void *zonfer;
}ELEM;
ELEM *stiva[MAX];
int istiva;
void orizontal(int,int);
void vertical(int,int,int,int);
void fereastra(int st,int sus,int dr,int jos,int fond,int
culoare,
int chenar,int n)
//Afiseaza o fereastra limitata de un chenar
{ extern ELEM *stiva[];
extern int istiva;
237
//memoreaza partea din ecran pe care se va afisa
fereastra
if(istiva==MAX){
printf("\nPrea multe ferestre!");
exit(1); }
if ((stiva[istiva]=(ELEM *)farmalloc(sizeof(ELEM)))==0){
printf("\nMemorie insuficienta\n");
exit(1); }
stiva[istiva]->x=st;
stiva[istiva]->y=sus;
stiva[istiva]->u=dr;
stiva[istiva]->v=jos;
if((gettext(st,sus,dr,jos,stiva[istiva]->zonfer))==0){
printf("\nEroare la memorarea ecranului!");
exit(1); }
istiva++;
//Activeaza fereastra si o afiseaza pe ecran
window(st,sus,dr,jos);
textattr(16*fond+culoare);
clrscr();
//Trasare chenar
if (chenar){
textcolor(WHITE);
highvideo();
switch(chenar){
case SIMPLU:
putch(218);
break;
case DUBLU:
putch(201);
break; }
orizontal(dr-st-2,chenar);
switch(chenar){
case SIMPLU:
putch(191);
break;
case DUBLU:
putch(187);
break; }
vertical(jos-sus,1,2,chenar);
gotoxy(1,jos-sus+1);
switch(chenar){
case SIMPLU:
putch(192);
break;
case DUBLU:
putch(200);

238
break; }
orizontal(dr-st-2,chenar);
vertical(jos-sus-1,dr-st,2,chenar);
gotoxy(dr-st,jos-sus+1);
switch(chenar){
case SIMPLU:
putch(217);
break;
case DUBLU:
putch(188);
break; }
normvideo();
textattr(16*fond+culoare); }
gotoxy(3,3);
cprintf("%d",n);
//Ascunde cursorul
_AH=1;
_CH=0x20;
geninterrupt(0x10); }
void orizontal(int a,int chenar)
{ while(a--)
switch(chenar){
case SIMPLU:
putch(196);
break;
case DUBLU:
putch(205);
break; } }
void vertical(int a,int col,int lin,int chenar)
{ while(a--) {
gotoxy(col,lin++);
switch(chenar){
case SIMPLU:
putch(179);
break;
case DUBLU:
putch(186);
break; }
} }

void main(void)
{ clrscr();
fereastra(4,4,60,20,3,10,2,6);
getch(); }

239
Capitolul XII

UTILIZAREA ECRANULUI
ÎN MOD GRAFIC

Pentru aplicaţiile grafice limbajul C pune la dispoziţie peste 60


de funcţii standard ce au prototipul în fişierul graphics.h. În
continuare sunt prezentate cele mai importante funcţii ce permit
gestiunea ecranului în mod grafic.
12.1. Iniţializarea modului grafic
Pentru a se putea lucra în mod grafic trebuie realizată o
iniţializare utilizând funcţia initgraph. Aceasta poate fi folosită
singură sau împreună cu o altă funcţie numită detectgraph care
determină parametrii adaptorului grafic. Prototipul ei este:
void far detectgraph(int far *gd, int far *gm);
unde:
Pointerul gd păstrează adresa uneia din valorile din tabelul
următor (în funcţie de adaptorul grafic utilizat):

Constantă simbolică Valoare


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

240
Zona spre care pointează gm memorează una din valorile:
Adaptor Constantă simbolică - Rezoluţie
Valoare
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:\\BORLANDC\\BGI”
Exemplu: Pentru setarea în mod implicit a modului grafic se poate
utiliza următoarea secvenţă de instrucţiuni:
……………………………
int gd,gm;
detectgraph(&gd,&gm);
initgraph(&gd,&gm, ”C:\\BORLANDC\\BGI”);
……………………………
Doar după apelul funcţiei initgraph pot fi utilizate şi alte
funcţii de gestionare a ecranului în mod grafic.

241
Utilizatorul poate defini el însuşi parametri pentru iniţializarea
modului grafic. De exemplu, secvenţa următoare:
……………………………
int gd=1,gm=0;
initgraph(&gd,&gm, ”C:\\BORLANDC\\BGI”);
……………………………
setează modul grafic corespunzător unui adaptor grafic CGA cu
rezoluţia 320*200 de puncte.
În afara acestor funcţii mai pot fi utilizate şi următoarele funcţii:
void far setgraphmode (int mode) – utilizată pentru setarea
modului grafic unde mode are valorile 0 – 4 pentru VGA, 0-1 pentru
EGA, 0 – 2 pentru VGA;
void far retorecrtmode(void) – ce permite revenirea la modul
precedent;
void far graphdefaults(void) – repune parametri grafici la
valorile implicite;
int far getgraphmode(void) – returnează codul modului grafic;
char *far getmodename(int mod) – returnează pointerul spre
numele modului grafic definit de codul numeric mod;
char *far getdrivername(void) – returnează pointerul spre
numele drieverului corespunzător adaptorului grafic curent;
void far getmoderange(int grafdriv,int far *min, int far *max)
– defineşte valorile minimale şi maximale ale modului grafic utilizat.
void far closegraph(void) – se utilizează pentru a ieşi din
modul grafic.
12.2. Gestiunea culorilor
Adaptoarele grafice sunt prevăzute cu o zonă de memorie în
care se păstrează date specifice gestiunii ecranului. Această zonă de
memorie poartă denumirea de memorie video.
În mod grafic, ecranul se consideră format din puncte luminoase
numite pixeli. Poziţia pe ecran a unui pixel se defineşte prin două
valori întregi: (x,y)
unde: x – defineşte coloana în care este afişat pixelul;
y – defineşte linia în care este afişat pixelul.
Fiecărui pixel îi corespunde o culoare ce este pătrată în
memoria video. Numărul maxim de culori care pot fi afişate cu
ajutorul unui adaptor EGA este 64.. Culorile se codifică prin numere
întregi din intervalul [0, 63] şi prin constante simbolice. Cele 64 de

242
culori nu pot fi afişate simultan. În cazul adaptorului EGA pe ecran se
pot afişa cel mult 16 culori ce formează o paletă. Paleta implicită este
dată de tabelul următor:
Denumire simbolică Valoare
BLACK 0
BLUE 1
GREEN 2
CYAN 3
RED 4
MANGETA 5
BROWN 6
LLIGHTGRAY 7
DARKGRAY 8
LIGHTBLUE 9
LIGHTGREEN 10
LIGHTCYAN 11
LIGHTRED 12
LIGHTMANGETA 13
YELLOW 14
WHITE 15
În mod implicit, culoarea fondului este întotdeauna cea
corespunzătoare indicelui zero, iar culoarea pentru desenare este cea
corespunzătoare indicelui 15.
Pentru controlul culorilor pot fi utilizate următoarele funcţii:
void far setbkcolor(int culoare) – modifică culoarea
fundalului;
int far getbkcolor(void) – returnează indexul din tabloul care
defineşte paleta pentru culoarea fundalului;
void far setcolor(int culoare) – setează culoarea utilizată
pentru desenare;
int far getcolor(void) – returnează indexul din tabloul care
defineşte paleta pentru culoarea de desenare;
void far setpalette(int index,int cod) – setează o nouă culoare
în paleta ce este utilizată la colorare (index ia valori între [0, 15] iar
cod între [0, 63]);
void far setallpalette(struct palettetype far* paleta) –
modifică mai multe culori din paletă. Palettetype este o structură
definită ca mai jos:
struct palettetype {
unsigned char size;
243
signed char colors[MAXCOLORS+1];
};
unde size – este dimensiunea paletei;
colors – este un tablou ale cărui elemente au ca valori
codurile culorilor componente ale paletei care se defineşte.
Modificarea paletei curente cu ajutorul funcţiei setpalette sau
setallpalette conduce la schimbarea corespunzătoare a culorilor afişate
pe ecran în momentul apelului funcţiilor respective.
void far getpalette(struct palettetype far* paleta) – determină
codurile culorilor componente ale paletei curente;
int far getmaxcolor(void) – returnează numărul maxim de
culori diminuat cu 1;
int far getpalettesize(void) – returnează numărul culorilor
componente ale paletei.
12.3. Setarea ecranului
În mod grafic, ecranul se compune din n*m puncte luminoase
(pixeli), adică pe ecran se pot afişa m linii a n pixeli fiecare. Poziţia
unui pixel este dată de două numere întragi (x,y) numite coordonatele
pixelului. Pixelul aflat în stânga sus are coordonatele (0,0). Coloanele
se numerotează de la stânga la dreapta, iar liniile de sus în jos.
Informaţii referitoare la ecran pot fi obţinute cu ajutorul
următoarelor funcţii:
int far getmaxx(void) – returnează coordonta maximă pe
orizontală;
int far getmaxy(void) – returnează coordonta maximă pe
verticală;
int far getx(void) – returnează poziţia pe orizontală a pixelului
curent;
int far gety(void) – returnează poziţia pe verticală a pixelului
curent.
12.4. Utilizarea textelor în mod grafic
Afişarea textelor în modul grafic presupune definirea unor
parametri care pot fi controlaţi prin intermediul funcţiilor descrise în
continuare:
a) void far settextstyle(int font,int direcţie,int charsize)
unde:
font – defineşte setul de caractere şi poate lua următoarele valori:
244
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.
Dimensiunile în pixeli ale unui şir de caractere se pot determina
utilizând funcţiile textheight şi textwidth:

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

246
- (dr,jos) – coordonatele colţului dreapta jos a zonei de pe ecran ce
se salvează;
- zt – pointer spre zona de memorie în care se salvează imaginea de
pe ecran.
unsigned far imagesize(int st, int sus, int dr, int jos) –
determină dimensiunea unei zone dreptunghiulare de pe ecran, unde:
- (st,sus) – coordonatele colţului 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:
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);

247
void far moverel(int dx, int dy) – mută cursorul în dreptul
pixelului de coordonate (x+dx,y+dy), unde (x,y) reprezintă
coordonatele pixelului curent;
void far line(int xi, int yi, int xf, int yf) – trasează un segment de
dreaptă între punctele de coordonate (xi,yi) şi (xf,yf);
void far lineto(int x, int y) – trasează un segment de dreaptă
între punctul curent şi punctul de coordonate (x,y);
void far linerel(int dx, int dy) – trasează un segment de dreaptă
între punctul curent şi punctul de coordonate (x+dx,y+dy), unde (x,y)
sunt coordonatele punctului curent;
void far arc(int xcentru, int ycentru, int unghistart, int
unghifin,int raza) – trasează un arc de cerc, unghiurile fiind exprimate
în grade sexagesimale;
void far circle(int xcentru, int ycentru, int raza) – trasează un
cerc, cu (xcentru,ycentru) coordonatele centrului şi raza raza acestuia;
void far ellipse(int xcentru, int ycentru, int unghistart, int
unghifin,int semiaxamare, int semiaxamică) – trasează un arc de
elipsă cu centrul în punctul de coordonate (xcentru,ycentru), semiaxa
mare definită de parametrul semiaxamare iar semiaxa mică definită de
parametrul semiaxamică;
void far rectangle(int st, int sus, int dr, int jos) – trasează un
dreptunghi definit de colţurile diagonal opuse;
void far drawpoly(int nr, int far* tabpct) – trasează o linie
polignală, parametrul nr specificând numărul de laturi iar tabpct este
un pointer spre un tablou de întregi ce definesc vârfurile liniei
poligonale păstrate sub forma: abscisa_i,ordonata_i unde i are valorile
1,2,…., nr+1;
void far setlinestyle(int stil, unsigned şablon, int grosime) –
defineşte stilul utilizat pentru trasarea liniilor, unde:
stil – este un întreg din intervalul [0,4] care defineşte stilul liniei
conform următorului tabel:
Constantă simbolică Valoare Stil
SOLID_LINE 0 Linie continuă
DOTTED_LINE 1 Linie punctată
CENTER_LINE 2 Linie întreruptă formată din liniuţe de
două dimensiuni
DASHED_LINE 3 Linie întreruptă formată din liniuţe de
aceeaşi dimensiune
USERBIT_LINE 4 Stil definit de utilizator prin şablon

248
şablon – defineşte stilul liniei şi are sens doar când parametrul
stil are valoarea 4;
grosime – defineşte lăţimea liniei în pixeli, astfel:
NORM_WIDTH – valoarea 1 pixel şi THICK_WIDTH – valoarea 3
pixeli.
void far getlinesettingstype(struct linesettingstype far*
linieinfo) – este utilizată pentru a determina stilul curent;
void far bar(int st, int sus, int dr, int jos) – are aceeaşi
semnificaţie cu funcţia rectangle însă dreptunghiul este colorat;
void far bar3d(int st, int sus, int dr, int jos, int profunzime, int
ind) – funcţia desenează o prismă colorată pentru ind diferit de zero;
pentru ind=0, nu se trasează partea de sus a prismei;
void far pieslice(int xcentru, int ycentru, int unghistart, int
unghifin,int raza) – desenează un sector de cerc colorat;
void far fillpoly(int nr, int far* tabpct) – desenează un
poligon colorat;
void far fillellipse(int xcentru, int ycentru, int semiaxamare,
int semiaxamică) – desenează o elipsă colorată;
void far setfillstyle(int haşura, int culoare) – defineşte modul
de colorare al figurilor, astfel:
culoare – defineşte culoarea utilizată pentru haşurare;
haşura – defineşte haşura utilizată pentru colorare conform
tabelului:
Constantă simbolică Valoare
EMPTY_FILL 0
SOLID_FILL 1
LINE_FILL 2
LTSLASH_FILL 3
SLASH_FILL 4
BKSLASH_FILL 5
LTBKSLASH_FILL 6
HATCH_FILL 7
XHATCH_FILL 8
INTERLEAVE_FILL 9
WIDE_DOT_FILL 10
CLOSE_DOT_FILL 11
USER_FILL 12
void far setfillpattern(char far *h_utilizator,int culoare) –
este utilizată pentru a defini o haşură a utilizatorului, astfel:
249
culoare – defineşte culoarea de haşurare;
h_utilizator – este un pointer spre o zonă de memorie care
defineşte haşura utilizatorului;
void far getfillsettings(struct fillsettingstype far* stilculoare)
– este utilizată pentru determinarea stilului curent de colorare;
void far floodfill(int x, int y, int culoare) – este o funcţie
utilizată pentru colorarea unui domeniu închis, astfel:
(x,y) – reprezintă coordonatele unui punct din interiorul
domeniului închis;
culoare – defineşte culoarea utilizată la trasarea conturului
figurii (interiorul este colorat în conformitate cu setările efectuate de
funcţia setfillstyle).
Exemplu: Prezentăm în acest exemplu un model de utilizare a
modului grafic pentru trasarea graficelor unor funcţii matematice
elementare.
#include <stdio.h>
#include <math.h>
#include <graphics.h>
#include <conio.h>
int x,y;
float a,b;
void desen(void) //functia care deseneaza axele si
//coloreaza ecranul
{ cleardevice();
setbkcolor(14);
setcolor(12);
line(0,y,2*x,y);
line(x,0,x,2*y);
line(2*x-4,y-4,2*x,y);
line(2*x-4,y+4,2*x,y);
line(x,0,x-4,4);
line(x,0,x+4,4); }
void interval(int l1, int l2) //functia care verifica
// daca intervalele pe care sunt definite functiile
// trigonometrice, sunt respectate
{ while ((a<l1)||(b>l2))
{ clrscr();
cleardevice();
setbkcolor(0);
printf("reintroduce-ti intervalul astfel incat sa fie
cuprins intre -1 si 1:\n ");
printf("a=");
scanf("%f",&a);
printf("\n");

250
printf("b=");
scanf("%f",&b);
printf("\n"); }
desen();}
void grafic(float (*trig)(float))//functia care traseaza
// graficul functiilor trigonometrice
{ float ymax,i,i1,h,y0,y1,lx,ly;
h=0.001*(b-a);
if (abs(a)>abs(b)) lx=(x-25)/(abs(a)+1);
else lx=(x-25)/(abs(b)+1);
ymax=0;
for(i=a;i<=b;i+=h)
if (ymax<abs(trig(i))) ymax=abs(trig(i));
if (ymax>y/2) ymax=y-25;
ly=(y-25)/(ymax+1);
if (ly>lx) ly=lx;
for(i=a;i<=b;i+=h)
{ y0=trig(i);
i1=i*lx ;
y1=y0*ly;
putpixel(x+i1,y-y1,4);} }
float sinx (float x)
{ return sin(x);}
float cosx(float x)
{ return cos(x);}
float tanx(float x)
{ return tan(x);}
float ctanx(float x)
{ return 1/tan(x);}
float acosx(float x)
{ return acos(x);}
float asinx(float x)
{ return asin(x);}
float atanx(float x)
{ return atan(x);}
float actanx(float x)
{ return atan(1/x);}
void main()
{ int p,l,t,dr=DETECT, modgr;
initgraph(&dr,&modgr,"c:\\borlandc\\bgi");
setbkcolor(1);
x=getmaxx()/2,y=getmaxy()/2;
p=0;
while(p==0)
{ setbkcolor(1);
p=1;
printf("1 : sin(x)\n");

251
printf("2 : cos(x)\n");
printf("3 : tg(x)\n");
printf("4 : ctg(x)\n");
printf("5 : arcsin(x)\n");
printf("6 : arccos(x)\n");
printf("7 : arctg(x)\n");
printf("8 : arcctg(x)\n");
printf("Alegeti nr. corespunzator functiei dorite: ");
scanf("%d",&t);
while(t<1 || t>8 )
{ printf("Reintroducet t-ul cuprins intre 1 si 8\n ");
printf("t=");
scanf("%d",&t);}
printf("Dati intervalul: \n");
do{
printf("a=");
scanf("%f",&a);
printf("\n");
printf("b=");
scanf("%f",&b);
printf("\n");
if (a>b) printf("Reintroduce-ti intervalul astfel incat a
sa fie mai mic ca b:\n ");
} while(a>b);
desen();
switch(t) {
case 1:grafic(sinx); break;
case 2:grafic(cosx); break;
case 3:grafic(tanx); break;
case 4:grafic(ctanx); break;
case 5:interval(-1,1);
grafic(asinx); break;
case 6:interval(-1,1);
grafic(acosx); break;
case 7:grafic(atanx); break;
case 8:grafic(actanx); break;
defalut: p=0 ; }
getch();
clrscr();
cleardevice();
setbkcolor(0);
printf("Doriti graficul altei functii? 1-DA 0-NU :");
scanf("%d", &l);
if (l==1){clrscr();cleardevice(); p=0;}}
closegraph(); }

252
Capitolul XIII

FUNCŢII MATEMATICE

Limbajul C conţine mai multe funcţii matematice care utilizează


argumente de tip double şi întorc valori de tip double.
Aceste funcţii se împart în următoarele categorii:
- funcţii trigonometrice;
- funcţii hiperbolice;
- funcţii exponenţiale şi logaritmice;
- alte tipuri.
Toate funcţiile matematice sunt incluse în fişierul antet
"math.h". Acesta mai conţine o serie de macrodefiniţii cum ar fi
EDOM, ERANGE şi HUGE_VAL. Macrodefiniţiile EDOM şi
ERANGE se găsesc în fişierul "errno.h" şi sunt constante întregi
diferite de zero, utilizate pentru a semnala erorile de domeniu şi de
plajă ale funcţiei. HUGE_VAL (aflată tot în "errno.h") este o valoare
pozitivă de tip double.
Dacă un argument al unei funcţii matematice nu este în
domeniul pentru care a fost definită funcţia, atunci funcţia întoarce 0
şi în domeniul de eroare, "errno" este modificat la EDOM.
Dacă o funcţie produce un rezultat prea mare pentru a fi
reprezentat printr-un double, apare o depăşire, funcţia returnând
HUGE_VAL cu semnul adecvat iar "errno" este modificat la
ERANGE. Dacă se produce subdepăşire, funcţia întoarce zero, iar
"errno" este modificat la ERANGE în funcţie de implementare.
13.1 Funcţii trigonometrice
- sin(x) , x în radiani - sinusul lui x;
- cos(x) , x în radiani - cosinusul lui x.
- tan(x) , x în radiani - tangenta lui x;
Exemplu: Programul următor afişează valorile sinusului, cosinusului
şi tangentei unghiului a[-1,+1] radiani, din 0.1 în 0.1.
# include <math.h>
void main() { double val = -1.0;

253
do {
printf("sinusul lui %f este %f\n", val, sin(val));
printf("cosinusul lui %f este %f\n",val, cos(val));
printf("tangenta lui %f este %f\n", val, tan(val));
val += 0.1;}
while (val <= 1.0); }

13.2 Funcţii trigonometrice inverse


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

13.3 Funcţii hiperbolice


- sinh(x) , x R - sinus hipebolic de x;
- cosh(x) , x R - cosinus hipebolic de x;
- tanh(x) , x R - tangenta hipebolica de x.
13.4 Funcţii exponenţiale şi logaritmice
- exp(x) , x R - exponentiala lui x.
- log(x) , x > 0 - logaritmul natural al lui x;
- log10(x) , x>0 - logaritmul zecimal al lui x.
Exemplu: printf ("Valoarea lui e este: %f", exp(1.0));
Exemplu: Programul următor afişează valorile logaritmului
natural şi logaritmului zecimal din 1 în 1 al numerelor de la 1 la 10.
# include <math.h>
void main() {
double val =1.0;
do{printf("%f %f %f\n",val,log(val),log10(val));

254
val ++; }
while (val < 11.0);}
- pow(x,y); funcţia calculeaza xy. O eroare de domeniu apare
dacă x = 0 şi y = 0 sau dacă x < 0 şi y nu este întreg.
Exemplu: Programul următor afişeaza primele 11 puteri ale lui 10.
# include <math.h>
void main() { double x =10.0, y = 0.0;
do {
printf ("%f\n", pow(x,y));
y ++;} while (val < 11.0);
}

13.5 Generarea de numere aleatoare


În multe aplicaţii este necesară generarea de numere aleatoare.
Pentru asemenea cazuri limbajul C dispune de două funcţii, rand şi
random, care returnează numere întregi aleatore.
Funcţia rand are următorul prototip:
int rand(void)
şi returnează un număr întreg, aleator, cuprins în intervalul de la 0 la
RAND_MAX (valoare definită în fişierul antet stdlib.h).
Funcţia random are prototipul:
int random(int val_maxima)
şi returnează un număr întreg, aleator, cuprins în intervalul [0,
val_maxima]. Pentru generarea de numere aleatoare în virgulă mobilă
se împarte rezultatul funcţiei random la o valoare întreagă. Următorul
program exemplifică utilizarea acestor funcţii:
Exemplu:
#include <stdio.h>
#include <stdlib.h>
void main(void)
{ int k;
printf(”Valorile furnizate de functia rand\n”);
for(k=0;k<100;k++)
printf(”%d ”,rand());
printf(”Valorile furnizate de functia random(100)\n”);
for(k=0;k<100;k++) printf(”%d ”,random(100));
printf(”Valori reale intre 0 si 1\n”);
for(k=0;k<10;k++) printf(”%f ”,random(10)/10.0);
printf(”Valori intregi intre -10 si 10\n”);
for(k=0;k<10;k++) printf(”%d ”,10-random(20));}

255
13.6 Alte tipuri de funcţii matematice

Nume funcţie Caracterizarea funcţiei


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

256
Capitolul XIV

ELEMENTE DE PROGRAMARE
AVANSATĂ

14.1 Gestionarea memoriei


Un calculator poate avea trei tipuri de memorie: convenţională,
extinsă şi expandată. În programare memoria constituie un factor
important ce influenţează viteza de lucru a programelor. Fiecare tip de
memorie are diferite viteze de acces, ceea ce afectează performanţa
programelor. Volumul şi tipul de memorie instalată poate fi
determinat utilizând comanda DOS: C:>MEM /CLASSIFY (pentru
versiuni ale sistemului de operare DOS mai mari de varianta 5).
Sistemul de operare DOS dispune de capacităţi de gestionare a
memoriei ce pot maximiza performanţele calculatorului.
14.1.1 Memoria convenţională
Primul PC compatibil IBM utiliza de obicei între 64Kb şi
256Kb memorie RAM (Read Only Memory). Pe atunci această
memorie era mai mult decât suficientă. Astăzi, memoria convenţională
a unui PC este formată din primul 1Mb de RAM. Programele DOS
rulează, în mod obişnuit, cu primii 640Kb de memorie convenţională.
PC-ul utilizează restul de 384Kb de memorie (numită memorie
rezervată sau memorie superioară) pentru memoria video a
calculatorului, driverele de dispozitive, alte dispozitive HARD mapate
în memorie şi BIOS (Basic Input-Output Services – servicii intrare-
ieşire de bază).
Sistemul de operare Windows utilizează modelul de memorie
virtuală pentru a gestiona memoria, ceea ce înseamnă că eliberarea
memoriei convenţionale nu are semnificaţie sub acest sistem de
operare. Însă, memoria convenţională este importantă când se rulează
programe în cadrul unei ferestre DOS sub Windows.
Structura memoriei convenţionale a unui calculator personal
este următoarea:

257
BIOS ROM
Memorie rezervată
Memorie video
COMMAND.COM

Memorie pentru programe

Intrări CONFIG.SYS
Nucleul DOS
Zona de comunicaţii BIOS
Vectori de întrerupere BIOS
PC-ul împarte memoria în blocuri de 64Kb numite segmente. În
mod obişnuit, programul utilizează un segment de cod (ce conţine
instrucţiunile programului) şi un al doilea segment de memorie pentru
date. Dacă un program este foarte mare compilatorul va trebui să
dispună de mai multe segmente de cod sau de date, sau de ambele.
Modelul de memorie defineşte numărul de segmente pe care le poate
folosi pentru fiecare. Modele sunt foarte importante deoarece, dacă se
utilizează un model de memorie necorespunzător, programul poate să
nu deţină suficientă memorie pentru execuţie. Compilatorul va alege
un model de memorie suficient de mare pentru a rula programul, însă
cu cât memoria utilizată este mai mare cu atât viteza de execuţie a
programului scade. Din această cauză trebuie ales modelul cel mai mic
pentru necesităţile programului. Majoritatea compilatoarelor acceptă
următoarele modele de memorie:
a) tiny – combină datele şi codul programului într-un singur
segment de 64Kb (este cel mai mic şi mai rapid model de memorie);
b) small – utilizează un segment de memorie pentru cod şi un
segment pentru date (este cel mai obişnuit model de memorie);
c) medium – utilizează un segment de 64Kb pentru date şi
două sau mai multe segmente pentru codul programului. În acest caz
datele sunt accesate rapid prin utilizarea de adrese near, în schimb
însă, apelurile de funcţii se fac utilizând adrese far;
d) compact – alocă un segment de 64Kb pentru codul
programului şi două sau mai multe segmente pentru date (este un
model utilizat pentru programe mici ce manipulează un număr mare
de date);

258
e) large – alocă mai multe segmente atât pentru date cât şi
pentru cod şi este cel mai lent model de memorie din cele prezentate
până acum. El trebuie utilizat doar ca ultimă resursă;
f) huge – este un model utilizat doar în cazul utilizării unor
matrici mai mari de 64Kb. Pentru a stoca o astfel de matrice
programul trebuie să utilizeze cuvântul cheie huge pentru a crea un
pointer, astfel:
int huge *matrice_uriaşă
după care programul trebuie să utilizeze funcţia halloc pentru alocarea
memoriei şi funcţia hfree pentru eliberarea acesteia. Exemplul următor
alocă o matrice de 400000 octeţi:
Exemplu:
#include <stdio.h>
#include <malloc.h>
void main(void)
{ long int k;
int huge *matrice_uriasa;
if ((matrice_uriasa=(int huge*) halloc(100000L,sizeof
(long int)))==NULL)
printf(”Eroare la alocarea matricii”);
else{
printf(”Completeaza matricea\n”);
for(k=0;k<100000L;k++)
matrice_uriasa[k]=k%32768;
for(k=0;k<100000L;k++)
printf(”%d ”,matrice_uriasa[k]);
hfree(matrice_uriasa); } }
Pentru selectarea unui anumit model de memorie se include, de
regulă, o opţiune în cadrul liniei de comandă a compilatorului.
Majoritatea compilatoarelor predefinesc o constantă specifică pentru a
determina modelul curent de memorie. În tabelul următor sunt
prezentate aceste constante definite de compilatoarele Microsoft C şi
Borland C:
Model de memorie Microsoft C Borland C
Small M_I86SM _SMALL_
Medium M_I86MM _MEDIUM_
Compact M_I86CM _COMPACT_
Large M_I86LM _LARGE_
Programul poate verifica modelul de memorie utilizat folosind
următoarea secvenţă de instrucţiuni:
#ifndef _MEDIUM_
printf(”Programul cere modelul de memorie medium\n”);

259
exit(1);
#endif
Atunci când un program trebuie să aloce memorie în mod
dinamic se utilizează fie funcţia malloc pentru a aloca memorie din
zona near (din segmentul curent), fie funcţia fmalloc pentru a aloca
memorie far.
14.1.2 Memoria expandată
În cazul programelor mari o memorie de numai 1Mb este
insuficientă. Pentru a permite accesul la mai mult de 1Mb de memorie,
companiile Lotus, Intel şi Microsoft au creat o specificaţie pentru
memoria expandată, care combină software şi o platformă specială de
memorie expandată pentru a „păcăli” PC-ul în scopul accesării unor
volume mari de memorie. Mai întâi în zona de memorie superioară se
alocă un bloc de 64Kb după care acest bloc de memorie este împărţit
în patru secţiuni de 16Kb, numite pagini în care se încarcă paginile
logice ale programului. De exemplu un program de 128Kb este
împărţit în opt pagini de 16Kb fiecare care sunt încărcate în funcţie de
necesităţile programului în zona rezervată de 64Kb.
14.1.3 Memoria extinsă
Calculatoarele cu procesoare peste 386 utilizează adresarea pe
32 de biţi ceea ce le dă posibilitatea de accesare directă de până la 4Gb
de memorie. Programatorii au numit memoria de peste 1Mb memorie
extinsă. Pentru a accesa memoria extinsă, trebuie încărcat un driver de
dispozitiv pentru memoria extinsă, care în DOS este de obicei
himem.sys. Pentru a utiliza însă memoria extinsă este necesară trecerea
la modul protejat de lucru al procesorului, mod de lucru în care datele
unui program nu pot fi scrise peste datele altui program ce rulează
simultan cu acesta.
14.1.4 Stiva
Stiva este o regiune de memorie în cadrul căreia programele
păstrează temporar datele pe durata execuţiei. De exemplu, atunci
când programele transmit parametri către o funcţie, C plasează aceşti
parametri în stivă. Când funcţia îşi încheie execuţia aceştia sunt scoşi
din stivă. Stiva este numită astfel deoarece ultimele valori depuse sunt
primele extrase. În funcţie de modelul de memorie utilizat, spaţiul de
memorie ocupat de stivă diferă. Valoarea minimă a stivei este 4Kb. În

260
cazul modelelor compact sau large, C alocă pentru stivă un întreg
segment de 64Kb. Dacă un program plasează în stivă mai multe
informaţii decât poate reţine aceasta, va apărea o eroare de depăşire a
stivei (stack-overflow). Dacă programul a dezactivat testarea stivei,
datele depuse în stivă pot fi suprapuse peste datele programului.
Exemplul următor prezintă modul de determinare a dimensiunii stivei
utilizând funcţia _stklen.
Exemplu:
#include <stdio.h>
#include <dos.h>
void main(void)
{
printf(”Dimensiunea stivei este de %d octeti”,_stklen);
}

14.2 Servicii DOS şi BIOS


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

261
Aşa cum se observă, BIOS este situat imediat deasupra
componentei hardware, serviciiile DOS deasupra serviciilor BIOS, iar
programele deasupra sistemului DOS.
Uneori însă, programele pot evita serviciile DOS şi BIOS şi pot
accesa direct o componentă hardware (cum este cazul memoriei
video).
Se recomandă ca ori de câte ori poate fi utilizată o funcţie de
bibliotecă C în locul unui serviciu DOS sau BIOS, aceasta să fie
utilizată pentru a mări portabilitatea programelor şi la calculatoarele ce
utilizează alte sisteme de operare (WINDOWS, UNIX, etc.). În acest
caz, programul nu va mai trebui modificat pentru a putea fi rulat sub
WINDOWS sau UNIX.
Toate versiunile de WINDOWS vor apela propriile lor servicii
de sistem. Însă, serviciile de sistem WINDOWS apelează până la
urmă serviciile BIOS penttru a accesa componentele hardware ale
cal;culatorului.
14.2.1 Serviciile BIOS
Prezentăm în continuare o serie de servicii BIOS ce pot fi
accesate utilizând funcţii de bibliotecă ale limbajului C.
1) accesul la imprimantă
Înainte ca un program să scrie ieşirea la imprimantă utilizând
indicatorul de fişier stdprn se poate face o verificare dacă imprimanta
este conectată şi dacă are hârtie utilizând funcţia biosprint din fişierul
antet bios.h:
int biosprint(int comanda,int octet,int nr_port)
unde comanda specifică una din următoarele operaţii:
0 – tipăreşte octetul specificat;
1 – iniţializează portul imprimantei;
2 – citeşte starea imprimantei.
Parametrul octet specifică valoarea ASCII a caracterului ce se
doreşte a fi scris la imprimantă iar nr_port specifică portul
imprimantei care poate fi 0 pentru LPT1, 1 pentru LPT2, ş.a.m.d.
Funcţia biosprint returnează o valoarea înteagă pe un octet ai
cărui biţi au următoarea semnificaţie:
0 – dispozitiv în pauză;
3 – eroare I/O;
4 – imprimantă selectată;
5 – lipsă hârtie;

262
6 – confirmare dispozitiv;
7 – dispozitivul nu este ocupat.
2) operaţii intrare/ieşire
Operaţiile intrare/ieşire de nivel jos pot fi realizate utilizând
funcţia biodisk ce are următoarea sintaxă:
int biodisk(int operatie, int unitate, int head, int
track, int sector, int nr_sector, void *buffer)
unde parametrul unitate precizează numărul unităţii, care este 0
pentru A, 1 pentru B, şi aşa mai departe. Parametrii head, track, sector
şi nr_sector precizează sectoarele fizice ale disculuice trebie scris sau
citit. Parametru buffer este un pointer la bufferul din care sunt citite
sau în care sunt scrise datele. Parametru operatie specifică funcţia
dorită astfel:
0 Iniţializează sistemul de disc
1 Returnează starea ultimei operaţii pe disc
2 Citeşte numărul precizat de sectoare
3 Scrie numărul precizat de sectoare
4 Verifică numărul precizat de sectoare
5 Formatează pista specificată
6 Formatează pista specificată şi marchează sectoarele defecte
7 Formatează unitatea începând cu pista specificată
8 Returnează parametrii unităţii de disc
9 Iniţializează unitatea de disc
10 Execută o citire lungă – 512 octeţi de sector plus patru suplimentari
11 Execută o scriere lungă – 512 octeţi de sector plus patru
suplimentari
12 Execută o poziţionare pe disc
13 Iniţializarea alternativă a discului
14 Citeşte bufferul sectorului
15 Scrie bufferul sectorului
16 Testează dacă unitatea este pregătită
17 Recalibrează unitatea
18 Execută diagnosticarea unităţii de RAM
19 Execută diagnosticarea unităţii
20 Execută diagnosticarea internă a controlerului
Dacă se execută cu succes, funcţia returnează valoarea 0. Dacă
apare o eroare, valoarea returnată precizează eroarea.
3) servicii de tastatură din BIOS
Pentru accesul la serviciile de tastatură din BIOS, C-ul pune la
dispoziţie funcţia _bios_keybrd ce are următoarea sintaxă:
unsigned _bios_keybrd(unsigned comanda)
263
unde parametrul comanda specifică operaţia dorită şi poate avea una
din următoarele valori:
_KEYBRD_READ Indică funcţiei să citească un caracter de la
tastatură
_KEYBRD_READY Determină dacă este prezent un caracter la
bufferul tastaturii. Dacă funcţia returnează
0, înseamnă că nici o intrare de la tastatură
nu este prezentă. Dacă valoarea returnată
este 0xFFFF, utilizatorul a apăsat CTRL C
_KEYBRD_SHIFTSTATUS Returnează starea tastelor de control:
Bit 7 – INS este activat
Bit 6 – CAPSLOCK este activat
Bit 5 – NUMLOCK este activat
Bit 4 – SCRLLOCK este activat
Bit 3 – ALT este apăsată
Bit 2 – CTRL este apăsată
Bit 1 – SHIFT stânga este apăsată
Bit 0 – SHIFT dreapta este apăsată
_NKEYBRD_READ Indică funcţiei să citească un caracter de la
tastatură, inclusiv tastele speciale, cum ar
fi tastele cu săgeţi
_NKEYBRD_READY Determină dacă este prezent un caracter la
bufferul tastaturii. Dacă funcţia returnează
0, înseamnă că nici o intrare de la tastatură
nu este prezentă. Dacă valoarea returnată
este 0xFFFF, utilizatorul a apăsat CTRL C
Funcţia acceptă inclusiv tastele speciale,
cum ar fi tastele cu săgeţi
_NKEYBRD_SHIFTSTATUS Returnează starea tastelor de control,
inclusiv a tastelor speciale:
Bit 15 – SYSREQ este activat
Bit 14 – CAPSLOCK este activat
Bit 13 – NUMLOCK este activat
Bit 12 – SCRLLOCK este activat
Bit 11 – ALT dreapta este apăsată
Bit 10 – CTRL dreapta este apăsată
Bit 9 – ALT stânga este apăsată
Bit 8 – CTRL stânga este apăsată
4) obţinerea listei cu echipamente din BIOS
Unele programe necesită determinarea caracteristicilor
hardware ale calculatorului. Pentru aceasta se utilizează funcţia
_bios_equiplist care are următoarea sintaxă:
264
unsigned _bios_equiplist(void);
Funcţia returnează o valoare pe 16 biţi a căror valoare are
următoarea semnificaţie:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
15:14 – numărul de imprimante paralele instalate (de la 0 la 3);
13 – imprimanta serială;
12 – adaptorul de jocuri;
11:10:9 – numărul de porturi seriale COM (de la 0 la 7);
8 – prezenţa DMA (Direct Memory Acces); bitul are valoarea 0 dacă
există DMA şi 1 dacă nu există;
7:6 – numărul drieverelor de disc;
5:4 – modul video: 00-neutilizat, 01-mod video 40x25 mono, 10-mod
video 80x25 color, 11-mod video 80x25 mono;
3:2 – dimensiunea memorie RAM: 00-16Kb, 01-32Kb, 10-48Kb, 11-
64Kb;
1 – prezenţa coprocesorului matematic;
0 – prezenţa unităţii de disc flexibile.
5) controlul intrărilor şi ieşirilor pentru portul serial
Pentru a executa operaţii intrare/ieşire utilizând portul serial se
utilizează funcţia bioscom ce are următoarea sintaxă:
unsigned bioscom(int comanda,int port,char octet);
Parametrul comanda specifică operaţia dorită şi poate avea una
din următoarele valori:
_COM_INIT Stabileşte valorile pentru comunicare ale portului
_COM_RECEIVE Primeşte un octet de la port
_COM_SEND Trimite un octet la port
_COM_STATUS Returnează valorile portului
Parametrul port specifică portul serial ce se doreşte a fi utilizat,
unde 0 corespunde lui COM1, 1 lui COM2 şi aşa mai departe.
Parametrul octet specifică fie octetul pentru ieşire, fie valorile
de comunicare dorite.
6) determinarea volumului de memorie convenţională BIOS
Pentru a determina memoria convenţională ce poate fi utilizată
de către un proggram se utilizează funcţia biosmemory ce are
următoarea sintaxă:
int biosmemory(void);
Valoarea returnată de această funcţie nu cuprinde memoria
extinsă, expandată sau superioară.
7) citirea cronometrului BIOS

265
BIOS are incorporat un ceas intern ce bate de 18.2 ori pe
secundă. Acest cronometru este util pentru a genera punctul iniţial al
unui generator de numere aleatoare. Multe compilatoare de C pun la
dispoziţie două funcţii pentru accesul la cronometrul BIOS: biostime
şi _bios_timeofday. Sintaxa acestor funcţii este următoarea:
long biostime(int operatie,long timp_nou);
Parametrul operaţie poate lua două valori:
0 – dacă se doreşte ca funcţia să citească valoarea curentă a
cronometrului;
1 – pentru a fixa valoarea cronometrului la valoarea timp_nou.
long _bios_timeofday(int operatie,long *batai);
Această funcţie poate fi, de asemenea, utilizată pentru a citi sau
a fixa cronometrul BIOS.
14.2.2 Serviciile DOS
În acest paragraf prezentăm o serie de servicii DOS ce pot fi
accesate utilizând funcţii de bibliotecă ale limbajului C.
1) suspendarea temporară a unui program
Execuţia unui program poate fi suspendată temporar utilizând
funcţia sleep.h din fişierul antet dos.h:
void sleep(unsigned secunde);
parametrul secunde specificând numărul de secunde pe care este
suspendat programul.
2) utilizarea sunetelor
Generarea de sunete ce utilizează difuzorul calculatorului se
realizează utilizând funcţiile sound şi nosound:
void sound(unsigned frecventa)
generează un sunet cu frecvenţa frecventa;
void sound(unsigned frecventa)
deconectează difuzorul. Programul următor generează un sunet de
sirenă dezactivat la apăsarea unei taste:
Exemplu:
#include <dos.h>
#include <conio.h>
void main()
{ unsigned frecventa;
do{ for (frecventa=500;frecventa<=1000;frecventa+=50)
{ sound(frecventa);
delay(50); }
for (frecventa=1000;frecventa>=500;frecventa-=50)
{ sound(frecventa);
delay(50); } }

266
while(!kbhit());
nosound(); }
3) obţinerea de informaţii despre erori în DOS
În cazul în care un serviciu al sistemului DOS eşuează,
programele pot cere informaţii suplimentare despre acea eroare
folosind funcţia dosexterr:
int dosexterr(struct DOSERROR *info_eroare);
unde structura DOSERROR are următoarele câmpuri:
struct DOSERROR{
int de_exterror; //eroare
int de_class; //clasa erorii
int de_action;//actiune recomandata
int de_locus;//sursa erorii };
Dacă funcţia returnează valoarea 0, apelul serviciului DOS nu a
avut nici o eroare.
Clasa erorii descrie categotia erorii, astfel:
01H Resurse depăşite
02H Eroare temporară
03H Eroare de autorizare
04H Eroare de sistem
05H Eroare hardware
06H Eroare de sistem nedatorată programului curent
07H Eroare de aplicaţie
08H Articol neîntâlnit
09H Format nevalid
0AH Articol blocat
0BH Eroare de suport
0CH Articolul există
0DH Eroare necunoscută
Parametrul de_action indică programului cum să răspundă
erorii, astfel:
01H Mai întâi încearcă din nou, apoi cere intervenţia utilizatorului
02H Încearcă din nou, cu o întârziere, apoi cere intervenţia
utilizatorului
03H Cere intervenţia utilizatorului pentru soluţie
04H Renunţă şi elimină
05H Renunţă, dar nu elimina
06H Ignoră eroarea
07H Încearcă din nou după intervenţia utilizatorului
Parametrul de_locus specifică sursa erorii, astfel:
267
01H Sursă necunoscută
02H Eroare de dispozitiv bloc
03H Eroare de reţea
04H Eroare de dispozitiv serial
05H Eroare de memorie
4) citirea valorilor registrului segment
Codul programului, datele şi stiva sunt controlate de compilator
utilizând patru registre de segment: CS, DS, ES, SS. În unele cazuri
este necesar să se cunoască valoarea acestor registre. Pentru astfel de
cazuri se utillizează funcţia segread:
void segread(struct SREGS *segs);
Structura SREGS are următoarele câmpuri:
struct SREGS
{ unsigned int es;
unsigned int cs;
unsigned int ss;
unsigned int ds; }
5) accesul la valorile de port
Pentrul controlul hardware de nivel inferior, compilatoarele de C
pun la dispoziţie următoarele funcţii:
- int inport (int adresa_port); - citeşte un cuvânt de la
portul specificat de parametrul adresa_port;
- int inportb (int adresa_port); - citeşte un octet de la
portul specificat de parametrul adresa_port;
- int outport (int adresa_port); - scrie un cuvânt de la
portul specificat de parametrul adresa_port;
- int outportb (int adresa_port); - scrie un octet de la
portul specificat de parametrul adresa_port;
6) suspendarea unui program
Pentru suspendarea unui program pe un anumit interval de timp
se poate utiliza funcţia delay, similară funcţiei sleep. Funcţia delay are
însă ca parametru o constantă exprimată în milisecunde:
void delay(unsigned milisecunde);
7) apelarea unei comenzi interne DOS
Pentru apelarea unei comenzi DOS sau a unui fişier pentru
comenzi se utilizează funcţia system:
int system(const char *comanda);
Parametrul comanda este un şir de caracter care conţine numele
comenzii DOS sau a fişierului de comenzi. Dacă funcţia reuşeşte să
execute comanda, se returnează valoarea 0, altfel returnează -1.
268
Programul următor prezintă utilizarea funcţiei system.
Exemplu:
#include <stdlib.h>
#include <stdio.h>
void main(void)
{ if(system("DIR"))
printf("EROARE!\n"); }
8) lucrul cu vectori de întrerupere
Un vector de întrerupere este o adresă de segment şi de
deplasament a codului care tratează o anumită întrerupere.
Determinarea vectorului de întrerupere se realizează utilizând funcţia
_dos_getvect în modul următor:
void interrupt(* _dos_getvect(unsigned nr_intr))();
Parametrul nr_intr specifică numărul întreruperii dorite ce poate
avea valori de la 0 la 255. Programul următor va afişa vectorii pentru
toate întreruperile calculatorului:
Exemplu:
#include <stdio.h>
#include <dos.h>
void main(void)
{ int k;
for(k=0;k<=255;k++)
printf(”Intrerupere: %x Vector %lx\n”,k,
_dos_getvect(k)); }
Dacă se doreşte crearea unui program de tratare a unei
întreruperi, vectorul de întrerupere trebuie atribuit acestui program.
Această atribuire se realizează cu ajutorul funcţiei _dos_setvect:
void _dos_setvect(unsigned nr_intr,
void interrupt(* handler)());
Parametrul nr_intr specifică întreruperea al cărui vector trebuie
modificat.
Pentru activarea şi dezactivarea întreruperilor se utilizează
funcţiile:
void _disable(void);
void _enable(void);
Dacă se doreşte reactivarea întreruperii originare se utilizează
funcţia _chain_interrupt:
void chain_interrupt(void(interrupt far *handler)());
Generarea unei întreruperi se realizează folosind funcţia
geninterrupt:
void geninterrupt(int intrerupere);
unde parametrul intrerupere specifică întreruperea generată.

269
14.3 Bibliotecile C
Dacă se examinează fişierele ce însoţesc un compilator C, se
remarcă multe fişiere cu extensia LIB. Aceste fişiere conţin biblioteci
obiect. Atunci când este compilat şi link-editat un program, editorul de
legături examinează fişierele LIB pentru a rezolva referinţele la
funcţii. Când sunt create funcţii utile ce sunt necesare şi în alte
programe, se pot construi biblioteci în care aceste funcţii să fie
păstrate.
14.3.1 Reutilizarea unui cod obiect
În cazul creării unei funcţii utile care se doreşte reutilizată, se
poate compila fişierul ce conţine funcţia respectivă pentru a crea codul
obiect (de exemplu din fişierul funcţie.c prin compilare se obţine
fişierul obiect funcţie.obj). Funcţia definită în acest fişier obiect poate
fi reutilizată în alt program utilizând următoarea instrucţiune:
C:\>bc fisier_nou.c funcţie.obj
Totuşi, acest mod de a reutiliza codul unor funcţii este destul de
dificil de utilizat în cazul în care se doreşte reutilizarea unui număr
mare de funcţii aflate în fişiere obiect separate.
14.3.2 Lucrul cu fişiere bibliotecă
Operaţiile acceptate de fişierele bibliotecă sunt următoarele:
- crearea unei biblioteci;
- adăugarea unuia sau mai multor fişiere obiect la bibliotecă;
- înlocuirea unui fişier obiect cu altul;
- ştergerea unuia sau mai multor fişiere obiect din bibliotecă;
- listarea rutinelor pe care le conţine biblioteca.
În funcţie de compilator, numele programului de bibliotecă şi
opţiunile liniei de comandă pe care programul le acceptă vor diferi. În
continuare prezentăm operaţiile ce pot fi realizate cu funcţiile
bibliotecă utilizând programul TLIB al compilatorului Borland C.
Presupunem că în urma compilării am creat fişierul obiect funcţie.obj
ce conţine o serie de funcţii pe care dorim să le păstrăm într-o
bibliotecă. Crearea unei bilioteci biblioteca.lib care să conţină acest
fişier obiect se realizează cu următoarea linie de comandă:
C:\>tlib biblioteca.lib + functie.obj
După ce fişierul bibliotecă a fost creat, funcţiile acestuia sunt
disponibile pentru compilarea şi legarea noilor programe.

270
Funcţia de biliotecă TLIB a compilatorului Borland C are
următoarea sintaxă:
tlib cale comandă, fişier
unde:
- cale – este un şir de caractere care specifică calea până la
bilioteca asupra căreia se efectuează operaţia;
- comandă – este formată dintr-un simbol şi numele unui fişier
obiect. Simbol poate fi unul din caracterele: + (adaugă un
modul la bibliotecă), - (elimină un modul din bibliotecă), *
(extrage un modul din bibliotecă într-un fişier cu acelaşi
nume, fără al elimina), -+ (înlocuieşte un modul din
bibliotecă), -* (extrage şi elimină un modul din bibliotecă);
- fisier – reprezintă numele fişierul în care se scrie ieşirea
operaţiei efectuate asupra bibliotecii.
14.3 Fişierele antet
Fiecare program foloseşte una sau mai multe instrucţiuni
#include pentru a cere compilatorului de C să folosească instrucţiunile
incluse într-un fişier antet. Când compilatorul întâlneşte o instrucţiune
#include în program, el compilează codul din fişierul antet ca şi cum
ar fi scris în fişierul sursă. Fişierele antet conţin definiţii frecvent
utilizate şi furnizează compilatorului informaţii referitoare la funcţiile
sale. Dacă la compilarea programului se afişează un mesaj de eroare,
avertizând că nu se poate deschide un anumit fişier antet, trebuie
verificat subdirectorul care conţine fişierele antet, pentru a vedea dacă
acel fişier există sau nu. Dacă se găseşte fişierul respectiv, în linia de
comandă din sistemul de operare DOS trebuie scrisă următoarea
instrucţiune:
C:\>SET INCLUDE=C:\BORLANDC\INCLUDE

271
BIBLIOGRAFIE

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

2. Auslander D.,Tham C., Real-time software for control: program


examples in C, Prentice Hall, 1990.

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


1988.

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


York, 1992.

5. Somnea D., Turturea D., Introducere în C++, Programarea


orientatã pe obiecte, Ed. Tehnicã, Bucureşti, 1993.

6. Marian Gh., Bãdicã C., Pãdeanu L., Limbajul PASCAL, Indrumar


de laborator, Reprografia Universitãţii din Craiova, 1993.

7. Negrescu L., Introducere în limbajul C, Editura


MicroInformatica, Cluj Napoca, 1993.

8. Petrovici V., Goicea F., Programarea în limbajul C, Editura


Tehnicã, Bucureşti, 1993.

9. Marian Gh., Muşatescu C., Laşcu M., Iordache Şt., Limbajul C,


Editura ROM TPT, Craiova, 1999.

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


SITECH, Craiova, 2001.

11. Zaharia, M.D., Structuri de date şi algoritmi. Exemple în


limbajele C şi C++, Ed. Albastră, Cluj Napoca, 2002.

12. Kernighan, B.W., Ritchie, D.M., The C programming languages,


Englewood. Cliffs, N.J. Prentice-Hall, 1978.

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


Bucureşti, 1995.

272

You might also like