Documente Academic
Documente Profesional
Documente Cultură
Carte C2003
Carte C2003
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
VII
Capitolul I
INTRODUCERE ÎN
ARHITECURA SISTEMELOR DE CALCUL
UPC
Unitatea de procesare
şi control Echipamente de intrare
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ă
Memoria
principală
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.
CALCULATOR
SISTEM DE
OPERARE
APLICAŢII
UTILIZATOR
Disk-drive
Sistem de
operare
Mouse
Monitor
Tastaturã Imprimantã
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).
Instrucţiunile în limbajul
de nivel înalt se introduc
de la tastaturã.
Tot ce se introduce de la
tastaturã este vizibil pe monitor
Editor de texte
(eventual incorporat în mediu)
Link-editare
(legarea tuturor modulelor necesare)
REPREZENTAREA DATELOR ÎN
CALCULATOR
High=’1’
Low=’0’
timp
Ca urmare a acestei asocieri spunem, prin abuz de limbaj, că un
calculator numeric prelucrează numere binare. Ca şi un număr
zecimal, un număr binar are mai multe cifre binare. Sistemul de
numeraţie binar folosit pentru reprezentarea informaţiei în calculatoare
este un sistem de numeraţie ponderal, întocmai ca sistemul de
numeraţie zecimal.
Reprezentarea naturală a numerelor la nivelul percepţiei umane
este cea zecimală, pe când reprezentarea proprie maşinilor de calcul
este cea binară. De aici rezultă necesitatea compatibilizării sau
interfaţării între aceste două moduri de reprezentare a numerelor. Cum
cele două sisteme de numeraţie sunt ponderale, o primă diferenţă este
aceea că sistemul zecimal foloseşte ca ponderi puterile întregi
(pozitive sau negative) ale lui 10 (zece) iar sistemul binar va folosi
puterile întregi (pozitive sau negative) ale lui 2.
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ă
37 37 0.37x102
-37 -37 -0.37x102
0.375 0.375 0.375x100
-0.375 -0.375 -0.375x100
0.00375 0.00375 0.375x10-2
-0.00375 -0.00375 -0.375x10-2
12.375 12.375 0.12375x102
-12.375 -12.375 -0.12375x102
În general dorim să obţinem rezultatele numerice ale
programelor pe care le concepem într-o formă de reprezentare
accesibilă. Totuşi, calculatorul trebuie informat asupra formatului de
reprezentare în care dorim să se afişeze datele necesare. Aceasta
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
ck b k
n 1
N b c n 1 b n 1 c n 2 b n 2 ... c1 b1 c 0 b 0
k 0
În continuare vom studia următoarele probleme:
- cum se face conversia unui număr din baza b 10 în baza
b2
- 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
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
0+1=1
27
1+0=1
1+1=0, transport 1 spre rangul următor
Astfel, să facem adunarea 37+25 în binar:
37 1 0 0 1 0 1+
25 11001
62 111110
37 1 0 0 1 0 1x
Se observă cum se obţine
25 rezultatul corect.
11001
Înmulţirea se face în mod asemănător,
1 0 0 1 0 1ca o adunare repetată.
Spre exemplu, să calculăm 37x25
100101
100101
925 1110 011101
29
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 5 10 =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 infinit de cifre se poate reprezenta într-o altă bază pe un
1
număr finit de cifre ( ex: 0.3333...3...10 0.13 ). Cum orice
3
reprezentare binară internă este pe un număr finit de biţi, numărul
poate să nu fie reprezentat exact în calculator, ci cu o anumită
aproximaţie. Acest lucru este decisiv pentru a înţelege importanţa
lungimii reprezentării numerelor în calculator. Cu cât un număr binar
30
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
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 ‘-‘
31
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
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 110 012 011 001
2510 10 0111 2
3710 1 00 10 12 01 00 101
3710 10 110 1 1
37 1011011
25 0110012 0011001
33
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) 0100101 1100111
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
34
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.11 2 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.
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ă
35
ş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
reprezentare, adică să determinăm magnitudinea (valorile minime şi
maxime pozitive şi negative) ce pot fi reprezentate în formatul
respectiv.
36
În limbajul C, există două tipuri de reprezentare pe care le
putem numi principale: tipul întreg şi tipul real, fiecare având şi
anumite particularizări. Astfel, tipul întreg (int) include şi tipul
caracter (char) iar tipul real (float) include şi tipul real extins
(double).
Tipurile de date le vom reprezenta de la simplu la complex, în
ordinea char, int, float, double.
Tipurile de bază sunt char, int, float, double şi cu ajutorul
modificatorilor de tip putem obţine diverse particularizări.
Modificatorii pot fi signed, unsigned, short, long.
Ca o generalitate, numerele sunt reprezentate intern luându-se
în considerare bitul de semn, deci implicit numerele întregi sau reale
au MSB bit de semn. Dacă se specifică explicit, prin modificatorul
unsigned, nu se mai consideră (interpretează) bitul de semn.
2.3.3.1 Tipul char
Codul ASCII (American Standard Code for Information
Interchange) este un cod de reprezentare a caracterelor. Prin caracter
înţelegem unităţile de bază care se pot tasta (intrări de la tastatură),
tipări la imprimantă sau afişa pe ecran. Tastatura reprezintă, de
exemplu, dispozitivul de intrare care conţine de fapt o întreagă
colecţie de caractere ce pot fi emise prin apăsarea unei taste. Pentru a
fi receptat, emis sau prelucrat de către calculator, fiecare caracter are
asociat un cod binar (o combinaţie de biţi) care îl identifică în mod
unic. Cum cu un octet putem codifica 2 8 = 256 caractere, octetul s-a
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
Bit de semn
Gama de reprezentare este cuprinsă între
7
max 2 1 127
128, 127
7
min 2 128
39
6 42 B 82 52 R 98 62 b 114 72 r
6
6 43 C 83 53 S 99 63 c 115 73 s
7
6 44 D 84 54 T 100 64 d 116 74 t
8
6 45 E 85 55 U 101 65 e 117 75 u
9
7 46 F 86 56 V 102 66 f 118 76 v
0
7 47 G 87 57 W 103 67 g 119 77 w
1
7 48 H 88 58 X 104 68 h 120 78 x
2
7 49 I 89 59 Y 105 69 i 121 79 y
3
7 4a J 90 5a Z 106 6a j 122 7a z
4
7 4b K 91 5b [ 107 6b k 123 7b {
5
7 4c L 92 5c \ 108 6c L 124 7c |
6
7 4d M 93 5d ] 109 6d M 125 7d }
7
7 4e N 94 5e ^ 110 6e n 126 7e ~
8
7 4f O 95 5f _ 111 6f o 127 7f ⌂
9
40
MSB
S b30 Octetul 1
Octetul 2
Octetul 3
b0 Octetul 4
LSB
Tipul int este identic cu signed int şi utilizează o reprezentare pe
4B a numerelor întregi cu semn. Reprezentarea pe 4 octeţi duce la
posibilitatea măririi gamei de reprezentare astfel:
max 231 1
3
3
; 231 2 230 2 210 2 103 2 109
min 231
S b14
b0
LSB
short int se va reprezenta pe 2B, sub forma
max 2 1
15
15
15
min 2
; 2 2 2 32 2 32768, 32767
5 10 10
.
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 8 1018 9.2234 1018
2 63 2 3 210
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
41
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
Se observă cum stocarea în calculator a unei date floating-point
necesită trei părţi:
- bitul de semn (sign)
- mantisa, fracţia (significand)
- exponent (exponent)
Folosind formatul specific I80386, în limbajul C se disting trei
tipuri de date reale:
- float , cu reprezentare pe 4 octeţi (32 biţi, double word)
- double, cu reprezentare pe 8 octeţi (64 biţi, quad word)
- long double, cu reprezentare pe 10 octeţi (80 biţi, ten word)
MSB
b31 b30
b0
LSB
Exponent = 11b Significand = 52b double
S
Bias = 3FFH=1023
79 78 64 63 0
Exponent = 15b Significand = 52b long
S
Bias = 3FFFH=16383 double
42
Î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
Exponent = 10000010 = 13010
Significand = 1001000…00
43
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.510 4148000016
În cazul în care dorim să reprezentăm numărul negativ –12.5, singurul
bit care se va modifica va fi bitul de semn, care devine 1. Astfel,
reprezentarea internă în format float a numărului negativ real –12.5
este:
Semn
1 1 0 0 0 0 0 1
0 1 0 0 1 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
LSB
12.510 C148000016
Dacă numărul 12.5 se reprezintă în formatul double, deci pe 8
octeţi, atunci reprezentarea sa internă se va realiza astfel:
- bitul de semn va fi 0
- exponentul nu va mai fi pe 8 biţi ca la tipul float, ci pe 11 biţi,
deci se va schimba şi bias, care va fi 1023. Atunci:
44
3 1023 1026 1024 2 10000000010
exponent 1 bias
- significand va fi acelaşi ca la tipul float, dar reprezentat pe 52
de biţi
Semn
0 1 0 0 0 0 0 0
0 0 1 0 1 0 0 1
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
LSB
12.510 402900000000000016
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
5.251 0 101.012 .10101 23
1.510 1.12 .11 21 ;
5.25 1.5 .10101 .11 231
.10101
.11
10101
10101
.0111111
5.25 1.5 .0111111 2 4 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 102412 256 1036 2.56 1038
Valoarea maximă exactă, calculată fără a aproxima ca mai sus:
38
210 1024 1000 103 este n max 3.4028 10
exponent _ bias min 0 exponent _ real min 0 127 127
nmin 2127 27 210
12
23 210 210
12
8 210
13
8 10 39
Valoarea pozitivă minimă exactă este n min 5.8775 10 39
La tipul double vom obţine:
exponent _ biasmax 2047 exponent _ realmax 2047 1023 1024
nmax 21024 24 210
102
16 1024 306 16 10306 1.6 10307
Valoarea maximă exactă este n max 1.7 10 308
exponent _ biasmin 0 exponent _ realmin 0 1023 1023
nmin 21023 23 210
102
.125 10306
46
Valoarea pozitivă minimă exactă este n min 1.1125 10 308
Efectuând aceleaşi consideraţii şi calcule pentru tipul long double,
n 1.1 10 4932
vom obţine
n
max
Capitolul III
48
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.
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;
49
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ă.
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
50
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)
{
…………
}
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.
51
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( )
}
52
C. În continuare sunt descrise caracterele şi simbolurile din mulţimea
caracterelor C şi utilizarea acestora.
3.3.1. Litere şi numere
Mulţimea caracterelor C include literele mari şi mici ale
alfabetului englez şi cifrele zecimale din sistemul de numere arabe.
Literele mari şi mici ale alfabetului englez sunt următoarele:
ABCDEFGHIJKLMNOPRSTUVWXYZ
abcdefghijklmnoprstuvwxyz
iar cifrele zecimale:
0 1 2 3 4 5 6 7 8 9.
Aceste litere şi cifre pot fi folosite pentru a forma constante,
identificatori şi cuvinte cheie. Compilatorul C prelucrează litere mari
şi mici în mod distinct.
3.3.2. Caractere whitespace
Spaţiul, tab-ul, linefeed (linie nouă), carriage return
(revenire la capătul rândului), form feed, tab-ul vertical şi newline
sunt numite caractere whitespace deoarece servesc pentru spaţiere
între cuvinte, aliniere la o nouă coloană, salt la linie nouă. Aceste
caractere separă instrucţiuni definite de utilizator, constante şi
identificatori, de celelalte instrucţiuni dintr-un program. Compilatorul
C ignoră caracterele whitespace dacă nu sunt folosite ca separatori sau
drept componente de constante, sau ca şiruri de caractere. Caracterele
whitespace sunt utilizate pentru a face programele mai lizibile.
Comentariile sunt de asemenea tratate ca whitespace.
3.3.3. Caractere speciale şi de punctuaţie
Caracterele speciale şi de punctuaţie din mulţimea
caracterelor C sunt folosite pentru mai multe scopuri. Tabelul următor
prezintă aceste caractere.
Aceste caractere au o semnificaţie specială pentru
compilatorul de C. Caracterele de punctuaţie din setul de
caractere reprezentabile C care nu apar în acest tabel pot fi
utilizate numai în şiruri, constante caracter şi comentarii.
Caracte Nume Caracte Nume
r r
, Virgulă ! Semnul
53
exclamării
. Punct | Bară verticală
; Punct şi virgulă / Slash
: Două puncte \ Backslash
? Semnul ~ Tilda
întrebării
’ Apostrof _ Underscore
” Ghilimele # Diez
( Paranteză % Procent
stânga
) Paranteză & Ampersand
dreapta
[ Paranteză ^ Săgeată sus
dreaptă stânga
] Paranteză * Asterisc
dreaptă dreapta
{ Acoladă stânga - Minus
} Acoladă dreapta = Egal
> Mai mare + Plus
< Mai mic
3.3.4. Secvenţe escape
Secvenţele escape sunt combinaţii speciale de caractere
formate din whitespace şi caractere negrafice constituite în şiruri şi
constante caracter. Ele sunt în mod tipic utilizate pentru a specifica
acţiuni precum carriage return şi tab pe terminale şi imprimante şi
pentru a furniza reprezentarea caracterelor care normal au înţeles
special, cum ar fi ghilimelele (”). O secvenţă escape constă dintr-un
backslash urmat de o literă sau combinaţii de cifre. Setul complet de
secvenţe escape cuprinde:
\a caracterul BEL - activare sunet
\b caracterul BS (backspace) - revenire cu un spaţiu
\f caracterul FF (form feed) - salt de pagină la imprimantă
\n caracterul LF (line feed) - rând nou
\r caracterul CR (carriage return) - revenire la coloana 1
\t caracterul HT (horizontal tab) - tab orizontal
\v caracterul VT (vertical tab) - tab vertical
\\ caracterul \ (backslash)
\" caracterul " (double qoute) - ghilimele
\' caracterul ' (single qoute) - apostrof
54
\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.
55
În mod implicit, numai primele 32 de caractere dintr-un nume
sunt luate în considerare, adică două nume sunt diferite dacă ele diferă
în primele 32 de caractere ale lor.
Exemple de nume: a, b1, a1b2c3, Fs, _hG, Nume, nUME, …
Se recomandă ca numele să fie sugestive, adică ele să sugereze
pe cât posibil scopul alegerii lor sau a datei pe care o reprezintă.
3.5. Cuvintele cheie ale limbajului C
În limbajul C există un număr de cuvinte care au o utilizare
predefinită, numite cuvinte cheie. Utilizatorul nu poate să utilizeze
aceste cuvinte pentru a denumi variabile sau funcţii într-un program.
Tabelul următor prezintă cuvintele cheie ale limbajului C:
Cuvintele cheie ale limbajului C
auto default float register struct volatile
break do for return switch while
case double goto short typedef char
else if signed union const enum
int sizeof unsigned continue extern long
static void
3.6. Constante
În C, constantele se referă la valori fixe pe care programul nu le
poate modifica. Constantele pot fi: întregi, în virgulă mobilă sau reale,
constante-caracter, constante-şir sau enumerări. Zero poate fi folosit
ca o constantă pentru tipurile pointer, iar şirurile de caractere sunt de
fapt constante de tip char[]. Este posibil, de asemenea, să se specifice
constante simbolice. O constantă simbolică este un nume a cărui
valoare nu poate fi modificată în domeniul său.
În C există trei feluri de constante simbolice:
1. orice valoare de orice tip poate fi folosită ca şi
constantă prin adaugarea cuvântului cheie const la definirea sa;
2. un set de constante întregi definite ca o enumerare;
3. orice nume de vector sau funcţie.
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
56
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 */
59
3.6.7. Enumerări
OPERANZI ŞI OPERATORI ÎN C
4.1. Operanzi
60
- numele unei variabile;
- numele unui tablou;
- numele unei structuri;
- numele unui tip;
- numele unei funcţii;
- elementele unui tablou;
- elementele unei structuri;
- o expresie inclusă între paranteze rotunde.
Unui operand îi corespunde un tip şi o valoare. Dacă tipul
operandului este bine precizat la compilare, valoarea operandului se
determină fie la compilare, fie la execuţie.
Exemple:
1. 6353 – este o constantă întreagă zecimală de tip int şi
reprezintă un operand constant de tip int.
2. float x2 – reprezintă declaraţia variabilei x2, iar
numele x2 reprezintă un operand de tipul float.
3. 0xa13d – este o constantă întreagă hexazecimală de
tip unsigned şi reprezintă un operand de tipul unsigned.
4. produs(a,b) – este un apel al funcţiei produs. Această
funcţie reprezintă un operand al cărui tip coincide cu tipul valori
returnate de funcţia produs.
4.2. Operatori
Operatorii pot fi unari sau binari în funcţie de numărul de
operanzi cărora li se aplică. Un operator unar se aplică unui singur
operand, iar un operator binar se aplică la doi operanzi. Operatorul
binar se aplică la operandul care îl precede imediat şi la care îl
urmează imediat.
Operatorii limbajului C nu pot avea ca operanzi constante şir
(şiruri de caractere). C are mai multe clase generale de operatori:
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.
61
4.2.1. Operatori aritmetici
Lista operatorilor aritmetici este următoarea:
+ reprezintă operatorul plus unar sau binar, în funcţie de context
- reprezintă operatorul minus unar sau binar, în funcţie de context
* reprezintă operatorul de înmulţire (binar)
/ reprezintă operatorul de împărţire (binar)
% reprezintă operatorul modulo (binar)
Operandul operatorului unar plus trebuie să fie de tip aritmetic
sau pointer, iar rezultatul este valoarea operandului. Un operand întreg
presupune o promovare a întregilor.
Operandul operatorului unar minus trebuie să fie de tip
aritmetic, iar rezultatul este numărul negativ corespunzător. Un
operand întreg presupune promovarea întregilor.
Operanzii operatorilor * şi / trebuie să fie de tip aritmetic, iar ai
lui % trebuie să fie de tip întreg. Operatorul binar / reprezintă câtul, iar
% oferă restul împărţirii primului operand la al doilea. Dacă al doilea
operand al operatorului / sau % este zero, rezultatul este nedefinit.
Pentru operanzi de tip întreg este adevărată egalitatea:
(a / b) * b + a % b = a
În expresii operatorii binari + şi - au aceeaşi precedenţă, care
însă este mai mică decât a grupului *, / şi %. Precedenţa ultimului
grup este mai mică decât cea a operatorilor unari + şi -. Folosirea
parantezelor în expresii poate schimba precedenţa între operatori în
timpul evaluării acestora.
Exemplu: Dacă a, b, c, d sunt variabile de tip int, atunci:
- expresia d * b % a este echivalentă cu (d * b) % a;
- expresia -a / d este echivalentă cu (-a) / d;
- expresia a=b=c=d-15 este echivalentă cu a=(b=(c=(d
-15)));
- expresia a%-b*c este echivalentă cu (a%(-b))*c;
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
62
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:
== 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).
63
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
64
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 11 1 1 0 1 1 0
66
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
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)
67
{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");}
Acest program realizează rotirea numărului 14710=100100112
cu 6 poziţii
10010011
00100111
01001110
10011100
00111001
01110010
11100100
68
Programul de mai sus funcţionează pentru numere
reprezentabile pe un octet (mai mici de 255). Dacă dorim să facem o
rotire pe doi octeţi, atunci se poate modifica programul de mai sus
după cum urmează:
# include <stdio.h>
union rotate {
char ch[3];
unsigned int i;
} rot;
void disp_binary();
void rotate_bit();
void main() {
register int t;
rot.i=17843;
for (t=0;t<7;t++) {
disp_binary(rot.i);
rotate_bit(&rot);}}
/* se defineste functia rotate_bit() */
void rotate_bit(union rotate *rot)
{rot->ch[2]=0;
rot->i=rot->i<<1;
if (rot->ch[2]) rot->i=rot->i|1;}
/* se defineste functia disp_binary() */
void disp_binary(int i)
{register int t;
for (t=32768;t>0;t=t/2)
if (i&t) printf("1");
else printf("0");
printf("\n");}
Operatorul " ~ " realizează complementul faţă de 1. O utilizare
interesantă a complementului faţă de 1 este aceea că ne permite să
vedem setul caracterelor extinse implementate în calculator:
# include <stdio.h>
# include <conio.h>
void main() {char ch;
do {ch = getch();
printf ("%c %c\n", ch, ~ch);} while (ch != 'q');}
69
trebuie privită ca o nouă expresie, numită expresie de asignare.
Valoarea ei este chiar valoarea expresiei din dreapta operatorului de
atribuire. Dacă într-o expresie se fac mai multe atribuiri, atunci
evaluarea se face de la dreapta la 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 : +, -, *, %, <<, >>, &, ^ ,
70
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
72
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
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
73
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.
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.
74
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ă = += -= *= /=
,
Capitolul V
75
INSTRUCŢIUNI
etichetă: instrucţiune
#include <stdio.h>
void main(void)
{
76
int nr=1;
eticheta: printf(”%d”, nr++);
if (nr<=100)
goto eticheta;
}
77
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); }
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");
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); }
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.
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'); } }
84
/* Pentru terminare se apasa tasta t */ } }
O altă caracteristică interesantă a ciclului for este aceea că nu se
impune definirea tuturor celor trei parametri ai ciclului for, oricare
dintre ei putând fi opţionali. De exemplu, ciclul următor se va executa
până când de la tastatura se introduce numărul 123:
for (x = 0; x != 123;) scanf("%d", &x);
Deoarece instrucţiunea de incrementare a lui x lipseşte, de fiecare dată
când ciclul se repetă, programul testează ca x să fie egal cu 123, dar
nu modifică pe x în nici un fel. Dacă de la tastatură se introduce 123,
condiţia buclei devine falsă şi ciclul se termină.
Exemplu: O variantă de calcul a lui n! ar fi următoarea:
# include <stdio.h>
void main (void)
{ int n, i;
long int factorial;
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 ");
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);
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
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().
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".
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.
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
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";
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
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]);
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-
102
şablon şi variabila-structură. O variabilă de tip structură se declară cu
ajutorul şablonului structurii.
Pentru a declara o variabilă actuală cu această structură vom scrie
struct addr addr_info;
Această linie declară variabila addr_info ca variabilă structură
de tip addr.
Limbajul C alocă suficientă memorie pentru a păstra toate
variabilele ce alcătuiesc o structură. De exemplu, memoria alocată
pentru structura addr_info va fi :
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;
103
. . . . . . . . . . . . . .
} variabile_structura;
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();}}}
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); }
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
114
În C o uniune este o zonă de memorie utilizată de mai multe
variabile ce pot fi diferite ca tip. Definitia uniunilor este similară celei
a structurilor.
Exemplu:
union tip_u {
int i;
char ch;};
O variabilă de acest tip poate fi declarată fie prin plasarea
numelui 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 */
115
FILE *fp {
putc(word ->ch[0]); // se scrie primul caracter
putc(word->ch[1]); // se scrie al doilea caracter }
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);
116
Nici acest program nu functionează:
gets (s);
strcpy (bani, s);
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;
117
void main() {printf("%d\n",sizeof(y));}
Deci compilatorul va reţine valoarea celei mai largi tipuri de
date din uniune.
Capitolul VII
POINTERI
118
void main (void) {
int *count_addr, count, val;
count = 100; /* int count are valoarea 100 */
count_addr = &count; /*count_addr indica spre count.
Aceasta instructiune plaseaza in count_addr adresa din
memorie a variabilei count, adresa care nu are nici o
legatura cu valoarea lui count */
val = count_addr; /* val preia valoarea de la adresa
count_addr. Aceasta instructiune plaseaza valoarea lui
count aflata la adresa count_addr în variabila val */
printf ("%d", val); /*Se va tipari numarul 100 */ }
Spre exemplu, să 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
119
Adresa Memoria
i 1200 123
j 1202 123
p 1204 1200
120
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.
Din cele de mai sus se observă că operaţia fundamentală care se
execută asupra unui pointer este indirectarea, ceea ce presupune
referirea la un obiect indicat de acel pointer.
Exemplu:
char c1 = 'a';
char *p = &c1; /* p contine adresa lui c1 */
char c2 = *p; /*c2 preia valoarea de la adresa p*/
printf ("c1 = %c c2 = %c", c1, c2);
Deci variabila indicată de p este c1, iar valoarea memorată în c1
este 'a', aşa încât valoarea lui *p atribuită lui c2 este 'a'.
Operaţii aritmetice efectuate asupra pointerilor
Utilizarea operatorilor de incrementare şi decrementare
Fie secvenţa:
int *p1; /* pointer la intreg */
p1++;
De fiecare dată când se incrementează p1, acesta va indica spre
următorul întreg. Astfel, dacă p1 = 2000, după efectuarea instrucţiunii
p1++, acesta va fi p1 = 2004 (va indica spre următorul întreg).
După fiecare incrementare a unui pointer, acesta va indica spre
următorul element al tipului său de bază.
După fiecare decrementare a unui pointer, acesta va indica spre
elementul anterior.
Valoarea pointerilor va fi crescută sau micşorată în concordanţă
cu lungimea tipului datelor spre care aceştia indică, aşa cum se poate
vedea în exemplul următor:
char *ch = 3000;
short int *i = 3000;
ch 3000 i
ch + 1 3001
ch + 2 3002 i+1
ch + 3 3003
ch + 4 3004 i+2
ch + 5 3005
ch + 6 3006 i+3
121
Memoria
Cum valoarea indirectată de un pointer este o l-valoare, ea poate
fi asignată şi incrementată ca orice altă variabilă. O l-valoare (left
value) este un operand care poate fi plasat în stânga unei operaţii de
atribuire. Verificaţi utilizarea pointerilor din programul următor:
# include <stdio.h>
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
123
int t;
p = (char far *) start; /*Conversie la un pointer*/
for (t = 0; ; t++, p++) {
if (!(t%16)) printf ("/n");
printf ("%2X ", *p);
/*Afiseaza in hexazecimal continutul locatiei
de memorie adresata cu *p*/
if (kbhit()) return;} // Stop cand se apasa orice tasta }
Programul introduce 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.
126
*(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,
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;
127
void main(void) {
int value;
p1 = stack; /* p1 preia adresa bazei stivei */
tos = p1; /* tos va contine varful stivei */
do { scanf ("%d", &value);
if (value != 0) push (value);
else printf ("Nr. extras din stiva este %d \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); }
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]); }
130
Adresă ---------> Adresă ---------> Valoare
131
*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.
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.
132
High Stiva
Memorie liberá
pentru alocare
(heap)
Variabile globale
(statice)
Low Program
Memoria
A doua metodă de alocare a memoriei, constă în utilizarea
funcţiilor de alocare dinamică malloc() şi free(). Prin această metodă,
un program alocă memorie pentru diversele informaţii în spaţiul
memoriei libere numită heap, plasată între programul util şi memoria
sa permanentă şi stivă. Se observă că stiva creşte în jos, iar
dimensiunea acesteia depinde de program.
Un program cu multe funcţii recursive va folosi mult mai intens
stiva în comparaţie cu un program ce nu utilizeaza funcţii recursive,
aceasta deoarece adresele de retur şi variabilele locale corespunzătoare
acestor funcţii sunt salvate în stivă.
Funcţiile malloc() şi free()
Aceste funcţii formează sistemul de alocare dinamică a
memoriei în C şi fac parte din fisierul antet <stdlib.h>. Acestea
lucrează împreună şi utilizează zona de memorie liberă plasată între
codul program şi memoria sa permanentă (fixă) şi vârful stivei, în
scopul stabilirii şi menţinerii unei liste a variabilelor memorate. De
fiecare dată când se face o cerere de memorie, funcţia malloc() alocă o
parte din memoria rămasă liberă. De fiecare dată când se face un apel
de eliberare a memoriei, funcţia free() eliberează memorie sistemului.
Declararea funcţiei malloc() se face sub forma:
void *malloc (int numar_de_bytes);
Aceasta întoarce un pointer de tip void, ceea ce înseamnă că trebuie
utilizat un şablon explicit de tip atunci când pointerul returnat de
malloc() se atribuie unui pointer de un alt tip. Dacă apelul lui malloc()
se execută cu succes, malloc() va returna un pointer la primul byte al
zonei de memorie din heap ce a fost alocată. Dacă nu este suficientă
133
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():
# 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);
} }
134
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.
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.
135
# include <stdio.h>
void actualizeaza();
void afiseaza(), delay();
struct tm { /* se defineste structura tm */
int ore;
int minute;
int secunde;};
void main()
{struct tm time; // Structura time de tip tm
time.ore = 0;
time.minute = 0;
time.secunde = 0;
for (;;) {
actualizeaza (&time);
afiseaza (&time); }}
void actualizeaza(t)
/*Versiunea 1- referirea explicita prin pointeri */
struct tm *t; {
(*t).secunde ++;
if ((*t).secunde == 60) {
(*t).secunde = 0;
(*t).minute ++; }
if ((*t).minute == 60) {
(*t).minute = 0;
(*t).ore ++;}
if ((*t).ore == 24) (*t).ore = 0;
delay();}
void afiseaza(t) // Se defineste functia afiseaza()
struct tm *t; {
printf ("%d : ", (*t).ore);
printf ("%d : ", (*t).minute);
printf ("%d ", (*t).secunde);
printf ("\n");}
void delay() /* Se defineste functia delay() */
{ long int t;
for (t = 1;t<140000000;++t);}
Pentru ajustarea duratei î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 :
136
(*t).ore
este echivalent cu
t -> ore
Atunci programul de mai sus se poate rescrie sub forma:
# include <stdio.h>
void actualizeaza();
void afiseaza(), delay();
struct tm { /* se defineste structura tm */
int ore;
int minute;
int secunde;};
void main()
{struct tm time; // Declara structura time de tip tm
time.ore = 0;
time.minute = 0;
time.secunde = 0;
for (;;) {
actualizeaza (&time);
afiseaza (&time); }}
void actualizeaza(t)
/*Versiunea 1- referirea explicita prin pointeri */
struct tm *t; {
t->secunde ++;
if (t->secunde == 60) {
t->secunde = 0;
t->minute ++; }
if (t->minute == 60) {
t->minute = 0;
t->ore ++;}
if (t->ore == 24) t->ore = 0;
delay();}
void afiseaza(t) // Se defineste functia afiseaza()
struct tm *t; {
printf ("%d : ", t->ore);
printf ("%d : ", t->minute);
printf ("%d ", t->secunde);
printf ("\n");}
void delay() /* Se defineşte funcţia delay() */
{ long int t;
for (t = 1;t<140000000;++t);}
137
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ă
î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
138
ş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:
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
139
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
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.
140
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;
};
141
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.
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 {
142
char name[20];
char street[30];
char city[15];
char state[10];
unsigned int zip;
struct addr *next;}TNOD;
TNOD *first, *last;
FILE *fp;
extern int create_list();
extern char menu();
extern void display_list(), insert(), erase();
extern int loadf_list(), savef_list();
extern TNOD *add_nod();
Cu ajutorul acestui fişier header se realizează definirea
şablonului structurii addr şi apoi, prin comanda typedef, se defineşte
un nou tip de date numit TNOD. First şi last sunt pointeri la structuri
de acest tip iar fp este pointer la fişier (file pointer).
Cu extern se definesc prototipurile unor funcţii care nu se găsesc în
fişierul bd_main.c ci în fişierul bd_bib.c unde sunt colectate toate
funcţiile pe care le folosim. Acest fişier are la rândul său un fişier
header numit local1.h care conţine:
# include <stdio.h>
# include <ctype.h>
# include <string.h>
# include <stdlib.h>
extern FILE *fp;
typedef struct addr {
char name[20];
char street[30];
char city[15];
char state[10];
unsigned int zip;
struct addr *next;}TNOD;
extern TNOD *first, *last;
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;
143
do {
printf ("\n(C)reate new list\n");
printf ("(L)oad list from file\n");
printf ("(S)ave to file\n");
printf ("(D)isplay list\n");
printf ("(I)nsert record\n");
printf ("(E)rase\n");
printf ("(Q)uit\n");
printf (" Alegeti optiunea: ");
gets(s);
ch=s[0];
} while (!strrchr("clsdieq",ch));
return tolower(ch); }
//meniu functia de stergere
char menu_del() {
char s[5],ch;
do {
printf ("(E)ntire list delete\n");
printf ("(F)irst record delete\n");
printf ("(L)ast record delete\n");
printf ("(I)ntermediate delete\n");
printf ("(Q)uit\n");
printf (" Option ? ");
gets(s);ch=s[0];
} while (!strrchr("efliq",ch));
return tolower(ch); }
//meniu functia de inserare
char menu_insert() {
char s[5],ch;
do {
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); }
145
Crearea unei liste simple înlănţuite
La început variabilele first şi last au valoarea NULL, lista fiind
vidă. Crearea unei liste se face prin funcţia create_list. Ea returnează 0
pentru eroare şi 1 dacă operaţia reuşeşte. Prin această funcţie se
iniţializează o listă dinamică în memorie, fără a face o citire a unei
baze de date create anterior. Aceasta este prima funcţie care se
apelează când construim o bază de date nouă.
/* Functia create_list() new */
void create_list() {
int n=sizeof(TNOD);
char ch='c';
TNOD *p;
first=last=NULL;
while ((p=(TNOD *)malloc(n))!=NULL)
{p=add_nod(p);
if (first==NULL){ /* prima creare */
first=last=p;
first->next=NULL;}
else {
last->next=p;
last=p;
last->next=NULL;}
printf ("Exit? (x): ");
if ((ch=getchar())=='x') break;}
if (p==NULL){
printf("Memorie insuficienta pentru 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() */
146
void display_list() {
TNOD *p;
disp_antet();
p=first;
if (p!=NULL)
do {
disp_nod(p);
p=p->next;
getchar();} while (p!=NULL);
else printf("Lista vida !\n");}
Funcţia display_list() apelează la funcţia disp_nod() care afişeză
o singură înregistrare. Dacă este nevoie de afişarea unui cap de tabel
(antet) se apelează la funcţia disp_antet().
Inserarea unor noduri în lista dinamică
Această operaţie presupune introducerea unor noduri
(înregistrări) fie la începutul listei înaintea primului nod, fie la sfârşitul
său (adăugare) după ultimul nod, fie între două noduri vecine nesituate
la extremităţi. Funcţiile listate mai jos realizează aceste sarcini.
// functia de inserare
void insert() {
char choice;
for (; ;) {
choice = menu_insert();
switch (choice) {
case 'f' : ins_first(); break;
case 'l' : ins_last(); break;
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() */
147
void ins_first() {
int n=sizeof(TNOD);
char ch='c',s[2];
TNOD *p;
while (ch!='x') {
p=(TNOD *)malloc(n);
p->next=first;
p=add_nod(p);
first=p;
printf("Exit? (x): ");
gets(s);ch=s[0];}}
// inserare dupa inregistrarea afisata
void ins_int() {
TNOD *p, *pi;
char ch='n', s[2];
disp_antet();
p=first;
while ((p!=NULL)&&(ch!='y'))
{ disp_nod(p);
printf("Here ? [y]: ");
gets(s);ch=s[0];
if (ch!='y') p=p->next;}
pi=(TNOD *)malloc(sizeof(TNOD));
pi=add_nod(pi);
pi->next=p->next;
p->next=pi;}
La inserarea unui nod, este nevoie ca acesta să fie legat de cele
între care se introduce cu ajutorul pointerilor next. Dacă se doreşte ca
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 (; ;) {
148
choice = menu_del();
switch (choice) {
case 'e' : del_list(); break;
case 'f' : del_first(); break;
case 'l' : del_last(); break;
case 'i' : del_int(); break;
case 'q' : break;} break;}}
// se sterge intreaga lista si se elibereaza memoria
void del_list() {
TNOD *p,*pu;
p=first;pu=p->next;
while (pu!=NULL) {free(p);
p=pu;pu=pu->next;}
first=NULL;last=NULL;}
// sterge prima inregistrare
void del_first() {
int n=sizeof(TNOD);
char ch='c',s[2];
TNOD *p,*pu;
while (ch!='x') {
p=first;pu=p->next;
free(p);
first=pu;
printf("Exit? (x): ");
gets(s);ch=s[0];}}
// stergere ultima inregistrare
void del_last() {
int n=sizeof(TNOD);
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];
149
if (ch='y') {pa->next=pu;
free(p);}
else {pa=p;p=pu;pu=pu->next;}}}
Capitolul VIII
FUNCŢII
151
Programul, modificat ca mai jos, va duce la o compilare fără
probleme:
Exemplu:
# include <stdio.h>
float max();
float x;
void main()
{ x = max(3.2, 4.1);
printf("max= %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.
152
a) După parcurgerea codului corespunzător funcţiei se revine în
programul apelant la instrucţiunea imediat următoare. Exemplu:
Aceasta funcţie tipăreşte un şir în ordine inversă:
# include <string.h>
void afis_invers(char s[]);
void main() {
char s[10];
printf("Introduceti un sir de caracrere de la
tastatura (max 10)\n");
scanf("%s",s);
afis_invers(s); }
void afis_invers(char s[])
{ register int t;
for (t = strlen(s)-1; t >= 0; t--)
printf("%c", s[t]);
printf("\n"); }
b) Al doilea mod de întoarcere dintr-o funcţie se realizează
utilizând funcţia return. Funcţia return poate fi folosită fără nici o
valoare asociată.
Exemplu: Funcţia următoare afişează rezultatele ridicării unui număr
întreg la o putere întreagă pozitivă:
power (baza, exp){
int baza, exp, i;
scanf("%d %d", &baza, &exp);
if (exp < 0) return; /* Functia se termina
daca exp e negativ */
i = 1;
for (; exp; exp--) i = baza * i;
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.
153
1) Funcţii "pure" sunt funcţiile care efectuează operaţii asupra
argumentelor şi returnează o valoare de bază pe acea operaţie.
Exemplu: sqrt() şi sin() returnează respectiv radăcina pătrată şi sinusul
argumentului.
2) A doua categorie de funcţii sunt cele care manipulează
informaţii şi întorc o valoare care arată reuşita sau eşecul acestei
manipulări. Un exemplu este fwrite() folosită pentru a scrie informaţii
pe disk. Dacă scrierea se face cu succes, fwrite() întoarce numărul de
octeţi înscrişi (ceruţi să se înscrie); orice altă valoare indică apariţia
unei erori.
3) A treia categorie de funcţii sunt cele care nu trebuie să
întoarcă o valoare explicită. De exemplu, funcţia printf() întoarce
numărul de caractere tipărite, număr care, de obicei, nu are o utilizare
ulterioară.
Dacă pentru o funcţie care returnează o valoare nu se specifică
o operaţie de atribuire, calculatorul va ignora valoarea returnată.
Exemplu: Considerăm următorul program care utilizează funcţia
mul():
# include <stdio.h>
mul();
void main (void){
int x, y, z;
x = 10; y = 20;
z = mul(x, y); //- primul apel al lui mul()
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
154
programului şi dacă acesta nu utilizează variabile globale sau date, el
nici nu poate afecta, nici nu va fi afectat de alte părţi ale programului.
Codul şi datele care sunt definite în interiorul unei funcţii nu pot
interacţiona cu codul şi datele definite în altă funcţie, deoarece cele
două funcţii au scopuri diferite.
În cadrul unei funcţii se deosebesc trei tipuri de variabile, astfel:
variabile locale, parametri formali şi variabile globale. Domeniul
unei funcţii determină modul în care alte părţi ale programului pot
avea acces la aceste trei tipuri de variabile stabilind şi durata de viaţă a
acestora.
8.4.1. Variabile locale
Variabilele declarate în interiorul unei funcţii se numesc
variabile locale. Variabilele locale pot fi referite numai prin
instrucţiuni interioare blocului în care au fost daclarate aceste
variabile. Variabilele locale nu sunt cunoscute în afara blocului în care
au fost daclarate, domeniul lor limitându-se numai la acest bloc. Mai
exact, variabilele locale există numai pe durata execuţiei blocului de
cod în care acestea au fost daclarate; deci o variabilă locală este creată
la intrarea în blocul său şi distrusă la ieşire. De obicei, blocurile de
program în care se declară variabilele locale sunt funcţiile. Implicit, o
variabilă locală este auto, deci se stochează în memoria stivă. Ea
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)
155
variabile locale, deci au atributul auto, acest cuvânt cheie nu se
utilizează.
De obicei, variabilele locale utilizate în interiorul unei funcţii se
declară la începutul blocului de cod al acestei funcţii. Acest lucru nu
este neapărat necesar, deoarece o variabilă locală poate fi declarată
oriunde în interiorul blocului în care se utilizează, dar înainte de a fi
folosită.
Exemplu: Considerăm următoarea funcţie:
func (){
char ch;
printf (" Continuam (y / n) ? : ");
ch = getche(); //Se preia optiunea de la tastatura
/* Daca raspunsul este yes */
if (ch == 'y') {
char s[80];
/* s se creeaza numai dupa intrarea in acest bloc */
printf (" Introduceti numerele: \n ");
gets (s);
prelucreaza_nr (s); /* Se prelucreaza numerele */
} }
Aici, func() creează variabila locală s la intrarea în blocul de cod
a lui if şi o distruge la ieşirea din acesta. Mai mult, s este cunoscută
numai în interiorul blocului if şi nu poate fi referită din altă parte,
chiar din altă parte a funcţiei func() care o conţine.
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) {
156
while (*s)
if (*s == c) return 1;
else s++;
return 0; }
void main() {
char s[10], c;
scanf("%c %s", &c, &s);
if (func(s, c))
printf("Caracterul se afla in sir\n");
else printf("Caracterul NU se afla in sir\n"); }
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)
{
. . . . . . . . . . .
}
157
/* temp preia variabila globala count */
temp = count;
func2();
printf ("count is %d",count); // Se va afisa 100
}
func2() /* se defineste functia func2() */
{ int count; /* count este local */
for (count = 1; count < 10; count ++)
printf ("%2d\n" , count); }
Se observă că, 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
160
doi întregi se pot utiliza două metode, una generală şi una specifică,
astfel:
General : Specific :
mul (x, y) int x, y;
int x, y mul ()
{ return (x * y);} { return (x * y);}
Când se doreşte realizarea produsului a oricăror doi întregi x şi y
se utilizează varianta generală a funcţiei, iar când se doreşte produsul
numai al variabilelor globale x şi y se utilizează varianta specifică.
Exemplu:
# include <stdio.h>
# include <string.h>
int count; // count este global intregului program
play(); // Prototipul pentru functia play()
void main(void) {
char sir[80];
// sir este variabila locala a functiei main()
printf("Introduceti un sir : \n");
gets(sir);
play(sir);
}
play(char *p) // Se declara functia play()
{ // p este local functiei play()
if (!strcmp(p, "add")) {
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); }
161
În C transmiterea argumentelor de la funcţia apelantă spre
funcţia apelată se face prin valori sau prin adrese.
a) În cazul transmiterii argumentului prin valoare, se realizează
copierea (atribuirea) valorilor fiecărui argument în (la) câte un
parametru formal al funcţiei apelate.
Exemplu: Se apelează o funcţie ce calculeaza pătratul unui număr
întreg.
# include <stdio.h>
square(); // Prototipul functiei sqrt()
void main(void) {
int t = 10;
printf("%d %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);
}
162
Prin &x şi &y, programul transferă adresele lui x şi y funcţiei
swap() şi nu valorile lui x şi y.
Un apel combinat, valoare-referinţă este prezentat în exemplul
următor:
# include <stdio.h>
void f();
void main (void) {
int x = 1, y = 1;
printf("x = %d, y = %d \n", x, y);
f(x,&y);}
void f(int val, int *ref) {
val++;
(*ref)++;
printf("x = %d, y = %d \n",val,*ref); }
163
for(i = 0; i < 10; i++) printf ("%d",num[i]); }
Aceasta funcţie declară pe num ca fiind un vector de întregi cu
dimensiune necunoscută. Deoarece limbajul C nu verifică
dimensiunea vectorilor, dimensiunea actuală a vectorului este
irelevantă ca parametru al funcţiei. §i de aceasta dată, num va fi
convertit la un pointer la întreg.
c) Ultima metodă prin care se poate declara un parametru tablou
este ca pointer, astfel:
display(int *num)
{ int i;
for (i = 0; i < 10; i++) printf ("%d", num[i]); }
Limbajul C permite acest tip de declaraţie deoarece putem indexa
orice pointer utilizând [].
Toate cele trei metode de declarare a unui tablou ca parametru
produc acelaşi rezultat: un pointer. Cu toate acestea, un element al
unui tablou folosit ca argument al unei funcţii va fi tratat ca orice altă
variabilă. Astfel, programul de mai sus poate fi rescris sub forma:
# include <stdio.h>
void main (void) {
int v[10], i;
for (i = 0; i < 10; i++) v[i] = i;
for (i = 0; i < 10; i++) display (v[i]); }
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);}
164
{ int j;
for (j=2; j<=i/2; j++)
if (i%j==0) return 0;
return 1; }
void display(int *p, int j) /* Tipareste un vector
de intregi */
{ int i;
for (i=0; i<j; ++i) printf("%d ", p[i]); }
Din cele de mai sus, trebuie reţinut că atunci când un tablou se
utilizează ca argument al unei funcţii, calculatorul transmite funcţiei
adresa de început a tabloului. Acest lucru constituie o excepţie a
limbajului C în convenţia de transmitere a parametrilor prin valoare.
Astfel, codul funcţiei poate acţiona asupra conţinutului tabloului şi îl
poate altera.
Exemplu: Programul următor va modifica conţinutul vectorului sir
din funcţia main() după apelul funcţiei afis_litmari().
# include <stdio.h>
# include <ctype.h>
afis_litmari();
void main (void) {
char sir[80];
gets(sir);
afis_litmari(sir);
printf("\n%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);}
165
afis_litmari(char *s)
/* Se defineste functia afis_litmari() */
{ register int t;
for (t = 0; s[t]; ++t)
printf("%c",toupper(s[t])); } //Nu se modifica continutul
sirului sir
Rezultatul rulării va fi de această dată:
abcbdefghijklmnoprstuvxyzw
ABCBDEFGHIJKLMNOPRSTUVXYZW
abcbdefghijklmnoprstuvxyzw
În aceasta variantă conţinutul tabloului ramâne nemodificat,
deoarece programul nu-i schimbă valoarea.
Un exemplu clasic de transmitere a tablourilor într-o funcţie îl
constituie funcţia gets() din biblioteca C standard. Prezentăm o
variantă simplificată a acestei funcţii numită xgets().
xgets(s)
char *s; {
char ch;
int t;
for (t = 0; t < 80; ++t) {
ch = getchar();
switch (ch) {
case '\n' :
s[t] = '\0'; /* terminare sir */
return;
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.
166
Parametrul argc conţine numărul argumentelor din linia de
comandă şi este un întreg. Întotdeauna acesta va fi cel puţin 1,
deoarece numele programului este codificat ca primul argument.
Parametrul argv este un pointer la un tablou de pointeri la
caractere. Fiecare element din acest tablou indică spre un argument
linie_comanda. Toate argumentele linie_comanda sunt şiruri.
Exemplu: Următorul program arată modul de utilizare al
argumentelor linie_comanda şi va afişa Hello urmat de numele
dumneavoastră, dacă vă introduceţi numele, imediat după numele
programului:
# include <stdio.h>
void main (argc, argv) // Numele programului
int argc;
char *argv[];
{if (argc != 2) {
printf (" Ati uitat sa va introduceti numele \n");
return; }
printf ("Hello %s !", argv[1]); }
Dacă acest program se numeşte ARG_LC.C şi numele
dumneavoastră este DAN, atunci, pentru a executa programul, în linia
de comandă, veţi tipări ARG_LC DAN. Ieşirea programului va fi
Hello DAN !.
Argumentele linie_comanda trebuie separate prin spaţiu sau
TAB şi nu prin virgulă, sau;.
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 */
167
{ int disp, count;
if (argc < 2) {
printf ("Trebuie introdusa lungimea numarului
in linia de comanda\n");
return; }
if (argc==3 && !strcmp(argv[2], "display"))
disp = 1;
else disp = 0;
for (count = atoi(argv[1]); count; --count)
if (disp) printf("%d ",count);
printf("%c",7); /* Se emite un beep */ }
Observaţie: Dacă în linia de comandă nu se specifică nici un
argument, programul va afişa un mesaj de eroare.
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); }
173
func (x, y); } /* Nu se afiseaza nepotrivire */
Utilizând recomandările ANSI-C din 1989, programul de mai
sus se poate scrie mai compact:
#include <stdio.h>
void func (int x, float y) /* Parametrii formali
includ tipul */
{ printf ("%f", y/(float) x); }
void main (void) {
int x, y;
x = 10; y = 10;
func (x, y); }//afisare avertisment de conversie
sau, folosind funcţia prototip:
#include <stdio.h>
void func(); /* Declarare prototip fara
parametri formali ! */
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); }
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
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.
182
Capitolul IX
PREPROCESAREA
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
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).
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
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.
197
Descriere Nume funcţie de Nume funcţie de
nivel inferior nivel superior
198
Tipul FILE este un tip structurat care depinde de sistemul de
operare. Dacă facem abstracţie de cazurile speciale de calculatoare tip
VAX sau U3B, pe majoritatea implementărilor tipul FILE se defineşte
prin următoarea structură:
typedef struct {
unsigned char *_ptr;
int _cnt;
unsigned char *_base;
char _flag;
char _file;
} FILE;
Variabila de tip FILE este creată şi gestionată de către suportul
pentru exploatarea fişierelor în limbajul C. În urma deschiderii unui
fişier, programul primeşte un pointer la variabila creată, deci un
pointer la o structură de tip FILE. Se spune că s-a deschis un stream
(flux de date). Toate operaţiile care se fac pe acest stream se referă la
fişierul asociat stream-ului.
În limbajul C există 5 stream-uri standard, definite în <stdio.h>:
FILE *stdin;
care se referă la dispozitivul standard de intrare (tastatura). Orice
operaţie de citire de la stream-ul stdin înseamnă citire de la tastatură.
Bufferul folosit are o dimensiune de 254 de caractere şi bufferul se
goleşte la tastarea NL (‘\n’). Se mai spune că stdin este cu buffer la
nivel de linie.
FILE *stdout;
care se referă la dispozitivul standard de ieşire (ecranul). Orice
operaţie de scriere la stream-ul stdout înseamnă scriere pe ecran. Spre
deosebire de stdin, stdout este ne-bufferizat deoarece orice scriere pe
ecran se face direct la scrierea unui caracter în fişierul stdout.
FILE *stderr;
care se referă la dispozitivul standard pentru afişarea mesajelor de
eroare (ecranul). Este ne-bufferizat.
FILE *stdprn;
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
199
La acest nivel operaţiile de prelucrare a fişierelor se execută fără
o gestiune automată a zonelor tampon, făcându-se apel direct la
sistemul de operare. Programatorul are în gestiune o zonă declarată
drept buffer şi trebuie să ţină cont de faptul că această bufferizare este
la nivel linie. Numele funcţiilor de nivel inferior, orientate pe text
(transfer de octeţi) încep de obicei cu _ (underline). Dacă un fişier se
deschide în modul text, atunci, în cazul citirii dintr-un fişier, secvenţa
de octeţi CR-LF (0DH, 0AH) este translatată (înlocuită) cu un singur
caracter LF, iar în cazul scrierii în fişier caracterul LF este expandat la
secvenţa CR-LF. De asemenea, în cazul MS-DOS sau Windows
CTRL/Z este interpretat în cazul citirii drept caracter de sfârşit de
fişier (EOF).
10.3.1. Deschiderea unui fişier
Orice fişier înainte de a fi prelucrat trebuie deschis, motiv
pentru care operaţia de deschidere a unui fişier este de mare
importanţă. Deschiderea unui fişier existent se realizează prin
intermediul funcţiei _open. La revenirea din ea se returnează un aşa
numit descriptor de fişier. Acesta este un număr întreg. El identifică în
continuare fişierul respectiv în toate operaţiile realizate asupra lui.
În forma cea mai simplă funcţia _open se apelează printr-o
expresie de atribuire de forma:
df = _open(spf,mod)
unde:
df – este un număr întreg care reprezintă descriptorul de fişier
spf – este specificatorul fişierului care se deschide
mod – defineşte modul de prelucrare a fişierului
Specificatorul de fişier este fie un şir de caractere, fie un pointer
spre un astfel de şir de caractere. Conţinutul şirului de caractere
depinde de sistemul de operare folosit. În cea mai simplă formă el este
un nume sau mai general o cale care indică plasamentul pe disc al
fişierului care se operează. Fişierele deschise la acest nivel pot fi
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ă
200
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:
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)
201
_O_TEXT Fişierul este de tip text, adică se prelucrează pe
caractere sau octeţi (implicit)
Menţionăm că în MSDN aceste variabile se mai numesc şi oflag
(open-flag) şi sunt definite în fişierul header FCNTL.H.
În cazul în care oflag este _O_CREAT, atunci este necesară
specificarea constantelor opţionale pmode, care se găsesc definite în
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 = 111 2),
grupul la care aparţine proprietarul poate citi şi executa fişierul (5 =
1012) iar alţi utilizatori pot numai executa fişierul (1 = 001 2). 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
202
fişierele disc care sunt deschise primesc pe rând valorile 3, 4,..etc.
până la numărul maxim admis de fişiere deschise.
Valoarea returnată -1 indică o eroare de deschidere, în care caz
variabila errno este setată la una din valorile:
EACCES – (valoare 13) s-a încercat deschiderea pentru scriere a
unui fişier read-only sau modul de partajare a fişierului nu permite
operaţia specificată sau calea nu specifică un nume de fişier ci de
director.
EEXIST – (valoare 17) flagurile _O_CREAT şi _O_EXCL sunt
specificate, dar numele de fişier este al unui fişier deja existent.
EINVAL – (valoare 22) unul dintre argumentele oflag sau
pmode sunt invalide.
EMFILE – (valoare 24) nu mai sunt disponibile specificatoare
de fişier (prea multe fişiere deschise).
ENOENT – (valoare 2) fişierul sau calea nu au fost găsite.
Variabila globală errno păstrează codurile de eroare folosite de
funcţiile perror (print error) sau strerror (string error) pentru tratarea
erorilor. Constantele manifest pentru aceste variabile sunt declarate în
STDLIB.H după cum urmează:
extern int _doserrno;
extern int errno;
errno este setată de o eroare într-un apel de funcţie la nivel de
sistem (la nivelul inferior). Deoarece errno păstrează valoarea setată
de ultimul apel, această valoare se poate modifica la fiecare apel de
funcţie sistem. Din această cauză errno trebuie verificată imediat
înainte şi după un apel care poate s-o modifice. Toate valorile errno,
definite drept constante manifest în ERRNO.H, sunt compatibile
UNIX. Valorile valide pentru aplicaţiile Windows pe 32 de biţi sunt
un subset al acestor valori UNIX. Valorile specificate mai sus sunt
valabile pentru aplicaţii Windows pe 32 de biţi.
La o eroare, errno nu este setată în mod necesar la aceeaşi
valoare cu codul erorii de sistem. Numai pentru operaţii de I/O se
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>
203
#include <sys/stat.h>
#include <io.h>
#include <stdio.h>
void main( void )
{
int fh1, fh2;
fh1 = _open( "OPEN.C", _O_RDONLY );
if( fh1 == -1 )
perror( "open failed on input file" );
else
{ printf( "open succeeded on input file\n" );
_close( fh1 );}
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
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
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 */
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
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 );
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);}
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
218
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
220
. . . . . . . . . . . . . . . . .
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
221
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];
int result;
222
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
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" );}
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 */
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 ); }}
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
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
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);}
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
236
coordonatele (left, top) – stânga sus şi (right, bottom) – dreapta jos la
adresa de memorie indicată de pointerul destination;
- int puttext( int left, int top, int right, int bottom, void *source
); - citeşte textul cuprins în dreptunghiul definit de coordonatele (left,
top) – stânga sus şi (right, bottom) – dreapta jos de la adresa de
memorie indicată de pointerul source;
- int movetext( int left, int top, int right, int bottom, int
destleft, int desttop ); - mută textul cuprins în dreptunghiul definit de
coordonatele (left, top) – stânga sus şi (right, bottom) – dreapta jos în
dreptunghiul cu coordonatele colţului din stânga sus (destleft,
desttop);
- void insline (void); - inserează o linie vidă în fereastra activă;
- int getch (void); - citeşte un caracter fără ecou de la tastatură,
adică după ce este citit caracterul nu mai este afişat pe ecran; funcţia
returnează codul ASCII al caracterului citit de la tastatură.
- 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)
237
//Afiseaza o fereastra limitata de un chenar
{ extern ELEM *stiva[];
extern int istiva;
//memoreaza partea din ecran pe care se va afisa
fereastra
if(istiva==MAX){
printf("\nPrea multe ferestre!");
exit(1); }
if ((stiva[istiva]=(ELEM *)farmalloc(sizeof(ELEM)))==0){
printf("\nMemorie insuficienta\n");
exit(1); }
stiva[istiva]->x=st;
stiva[istiva]->y=sus;
stiva[istiva]->u=dr;
stiva[istiva]->v=jos;
if((gettext(st,sus,dr,jos,stiva[istiva]->zonfer))==0){
printf("\nEroare la memorarea ecranului!");
exit(1); }
istiva++;
//Activeaza fereastra si o afiseaza pe ecran
window(st,sus,dr,jos);
textattr(16*fond+culoare);
clrscr();
//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);
238
break;
case DUBLU:
putch(200);
break; }
orizontal(dr-st-2,chenar);
vertical(jos-sus-1,dr-st,2,chenar);
gotoxy(dr-st,jos-sus+1);
switch(chenar){
case SIMPLU:
putch(217);
break;
case DUBLU:
putch(188);
break; }
normvideo();
textattr(16*fond+culoare); }
gotoxy(3,3);
cprintf("%d",n);
//Ascunde cursorul
_AH=1;
_CH=0x20;
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
240
adaptorul grafic prezent la calculator şi păstrează valoarea
corespunzătoare acestuia în zona spre care pointează gd.
Zona spre care pointează gm memorează una din valorile:
Adaptor Constantă simbolică - Rezoluţie
Valoare
CGA CGAC0 – 0 320*200
CGAC1 – 1 320*200
CGAC2 – 2 320*200
CGAC3 – 3 320*200
CGAHI – 4 640*200
EGAHI – 1 640*350
VGAMED – 1 640*350
VGAHI – 2 640*480
241
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.
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.
242
În mod grafic, ecranul se consideră format din puncte luminoase
numite pixeli. Poziţia pe ecran a unui pixel se defineşte prin două
valori întregi: (x,y)
unde: x – defineşte coloana în care este afişat pixelul;
y – defineşte linia în care este afişat pixelul.
Fiecărui pixel îi corespunde o culoare ce este pătrată în
memoria video. Numărul maxim de culori care pot fi afişate cu
ajutorul unui adaptor EGA este 64.. Culorile se codifică prin numere
întregi din intervalul [0, 63] şi prin constante simbolice. Cele 64 de
culori nu pot fi afişate simultan. În cazul adaptorului EGA pe ecran se
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;
243
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;
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;
244
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:
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.
245
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:
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.
246
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ă;
- (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
247
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
249
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:
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;
250
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");
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)
251
{ 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");
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();
252
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(); }
Capitolul XIII
FUNCŢII MATEMATICE
253
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;
do {
printf("sinusul lui %f este %f\n", val, sin(val));
printf("cosinusul lui %f este %f\n",val, cos(val));
printf("tangenta lui %f este %f\n", val, tan(val));
val += 0.1;}
while (val <= 1.0); }
254
printf("arctg lui %f este %f\n", val, asin(val));
val += 0.1;
}
while (val <= 1.0); }
255
ş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));}
Capitolul XIV
ELEMENTE DE PROGRAMARE
AVANSATĂ
257
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:
BIOS ROM
Memorie rezervată
Memorie video
COMMAND.COM
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
258
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);
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{
259
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”);
exit(1);
#endif
260
14.1.3 Memoria extinsă
Calculatoarele cu procesoare peste 386 utilizează adresarea pe
32 de biţi ceea ce le dă posibilitatea de accesare directă de până la 4Gb
de memorie. Programatorii au numit memoria de peste 1Mb memorie
extinsă. Pentru a accesa memoria extinsă, trebuie încărcat un driver de
dispozitiv pentru memoria extinsă, care în DOS este de obicei
himem.sys. Pentru a utiliza însă memoria extinsă este necesară trecerea
la modul protejat de lucru al procesorului, mod de lucru în care datele
unui program nu pot fi scrise peste datele altui program ce rulează
simultan cu acesta.
14.1.4 Stiva
Stiva este o regiune de memorie în cadrul căreia programele
păstrează temporar datele pe durata execuţiei. De exemplu, atunci
când programele transmit parametri către o funcţie, C plasează aceşti
parametri în stivă. Când funcţia îşi încheie execuţia aceştia sunt scoşi
din stivă. Stiva este numită astfel deoarece ultimele valori depuse sunt
primele extrase. În funcţie de modelul de memorie utilizat, spaţiul de
memorie ocupat de stivă diferă. Valoarea minimă a stivei este 4Kb. În
cazul modelelor compact sau large, C alocă pentru stivă un întreg
segment de 64Kb. Dacă un program plasează în stivă mai multe
informaţii decât poate reţine aceasta, va apărea o eroare de depăşire a
stivei (stack-overflow). Dacă programul a dezactivat testarea stivei,
datele depuse în stivă pot fi suprapuse peste datele programului.
Exemplul următor prezintă modul de determinare a dimensiunii stivei
utilizând funcţia _stklen.
Exemplu:
#include <stdio.h>
#include <dos.h>
void main(void)
{
printf(”Dimensiunea stivei este de %d octeti”,_stklen);
}
264
_NKEYBRD_READY Determină dacă este prezent un caracter la
bufferul tastaturii. Dacă funcţia returnează
0, înseamnă că nici o intrare de la tastatură
nu este prezentă. Dacă valoarea returnată
este 0xFFFF, utilizatorul a apăsat CTRL C
Funcţia acceptă inclusiv tastele speciale,
cum ar fi tastele cu săgeţi
_NKEYBRD_SHIFTSTATUS Returnează starea tastelor de control,
inclusiv a tastelor speciale:
Bit 15 – SYSREQ este activat
Bit 14 – CAPSLOCK este activat
Bit 13 – NUMLOCK este activat
Bit 12 – SCRLLOCK este activat
Bit 11 – ALT dreapta este apăsată
Bit 10 – CTRL dreapta este apăsată
Bit 9 – ALT stânga este apăsată
Bit 8 – CTRL stânga este apăsată
4) obţinerea listei cu echipamente din BIOS
Unele programe necesită determinarea caracteristicilor
hardware ale calculatorului. Pentru aceasta se utilizează funcţia
_bios_equiplist care are următoarea sintaxă:
unsigned _bios_equiplist(void);
Funcţia returnează o valoare pe 16 biţi a căror valoare are
următoarea semnificaţie:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
15:14 – numărul de imprimante paralele instalate (de la 0 la 3);
13 – imprimanta serială;
12 – adaptorul de jocuri;
11:10:9 – numărul de porturi seriale COM (de la 0 la 7);
8 – prezenţa DMA (Direct Memory Acces); bitul are valoarea 0 dacă
există DMA şi 1 dacă nu există;
7:6 – numărul drieverelor de disc;
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
265
Pentru a executa operaţii intrare/ieşire utilizând portul serial se
utilizează funcţia bioscom ce are următoarea sintaxă:
unsigned bioscom(int comanda,int port,char octet);
Parametrul comanda specifică operaţia dorită şi poate avea una
din următoarele valori:
_COM_INIT Stabileşte valorile pentru comunicare ale portului
_COM_RECEIVE Primeşte un octet de la port
_COM_SEND Trimite un octet la port
_COM_STATUS Returnează valorile portului
Parametrul port specifică portul serial ce se doreşte a fi utilizat,
unde 0 corespunde lui COM1, 1 lui COM2 şi aşa mai departe.
Parametrul octet specifică fie octetul pentru ieşire, fie valorile
de comunicare dorite.
6) determinarea volumului de memorie convenţională BIOS
Pentru a determina memoria convenţională ce poate fi utilizată
de către un proggram se utilizează funcţia biosmemory ce are
următoarea sintaxă:
int biosmemory(void);
Valoarea returnată de această funcţie nu cuprinde memoria
extinsă, expandată sau superioară.
7) citirea cronometrului BIOS
BIOS are incorporat un ceas intern ce bate de 18.2 ori pe
secundă. Acest cronometru este util pentru a genera punctul iniţial al
unui generator de numere aleatoare. Multe compilatoare de C pun la
dispoziţie două funcţii pentru accesul la cronometrul BIOS: biostime
şi _bios_timeofday. Sintaxa acestor funcţii este următoarea:
long biostime(int operatie,long timp_nou);
Parametrul operaţie poate lua două valori:
0 – dacă se doreşte ca funcţia să citească valoarea curentă a
cronometrului;
1 – pentru a fixa valoarea cronometrului la valoarea timp_nou.
long _bios_timeofday(int operatie,long *batai);
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
266
Execuţia unui program poate fi suspendată temporar utilizând
funcţia sleep.h din fişierul antet dos.h:
void sleep(unsigned secunde);
parametrul secunde specificând numărul de secunde pe care este
suspendat programul.
2) utilizarea sunetelor
Generarea de sunete ce utilizează difuzorul calculatorului se
realizează utilizând funcţiile sound şi nosound:
void sound(unsigned frecventa)
generează un sunet cu frecvenţa frecventa;
void sound(unsigned frecventa)
deconectează difuzorul. Programul următor generează un sunet de
sirenă dezactivat la apăsarea unei taste:
Exemplu:
#include <dos.h>
#include <conio.h>
void main()
{ unsigned frecventa;
do{ for (frecventa=500;frecventa<=1000;frecventa+=50)
{ sound(frecventa);
delay(50); }
for (frecventa=1000;frecventa>=500;frecventa-=50)
{ sound(frecventa);
delay(50); } }
while(!kbhit());
nosound(); }
3) obţinerea de informaţii despre erori în DOS
În cazul în care un serviciu al sistemului DOS eşuează,
programele pot cere informaţii suplimentare despre acea eroare
folosind funcţia dosexterr:
int dosexterr(struct DOSERROR *info_eroare);
unde structura DOSERROR are următoarele câmpuri:
struct DOSERROR{
int de_exterror; //eroare
int de_class; //clasa erorii
int de_action;//actiune recomandata
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
267
04H Eroare de sistem
05H Eroare hardware
06H Eroare de sistem nedatorată programului curent
07H Eroare de aplicaţie
08H Articol neîntâlnit
09H Format nevalid
0AH Articol blocat
0BH Eroare de suport
0CH Articolul există
0DH Eroare necunoscută
Parametrul de_action indică programului cum să răspundă
erorii, astfel:
01H Mai întâi încearcă din nou, apoi cere intervenţia utilizatorului
02H Încearcă din nou, cu o întârziere, apoi cere intervenţia
utilizatorului
03H Cere intervenţia utilizatorului pentru soluţie
04H Renunţă şi elimină
05H Renunţă, dar nu elimina
06H Ignoră eroarea
07H Încearcă din nou după intervenţia utilizatorului
Parametrul de_locus specifică sursa erorii, astfel:
01H Sursă necunoscută
02H Eroare de dispozitiv bloc
03H Eroare de reţea
04H Eroare de dispozitiv serial
05H Eroare de memorie
4) citirea valorilor registrului segment
Codul programului, datele şi stiva sunt controlate de compilator
utilizând patru registre de segment: CS, DS, ES, SS. În unele cazuri
este necesar să se cunoască valoarea acestor registre. Pentru astfel de
cazuri se utillizează funcţia segread:
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
268
Pentrul controlul hardware de nivel inferior, compilatoarele de C
pun la dispoziţie următoarele funcţii:
- int inport (int adresa_port); - citeşte un cuvânt de la
portul specificat de parametrul adresa_port;
- int inportb (int adresa_port); - citeşte un octet de la
portul specificat de parametrul adresa_port;
- int outport (int adresa_port); - scrie un cuvânt de la
portul specificat de parametrul adresa_port;
- int outportb (int adresa_port); - scrie un octet de la
portul specificat de parametrul adresa_port;
6) suspendarea unui program
Pentru suspendarea unui program pe un anumit interval de timp
se poate utiliza funcţia delay, similară funcţiei sleep. Funcţia delay are
însă ca parametru o constantă exprimată în milisecunde:
void delay(unsigned milisecunde);
7) apelarea unei comenzi interne DOS
Pentru apelarea unei comenzi DOS sau a unui fişier pentru
comenzi se utilizează funcţia system:
int system(const char *comanda);
Parametrul comanda este un şir de caracter care conţine numele
comenzii DOS sau a fişierului de comenzi. Dacă funcţia reuşeşte să
execute comanda, se returnează valoarea 0, altfel returnează -1.
Programul următor prezintă utilizarea funcţiei system.
Exemplu:
#include <stdlib.h>
#include <stdio.h>
void main(void)
{ if(system("DIR"))
printf("EROARE!\n"); }
8) lucrul cu vectori de întrerupere
Un vector de întrerupere este o adresă de segment şi de
deplasament a codului care tratează o anumită întrerupere.
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>
269
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ă.
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
270
Totuşi, acest mod de a reutiliza codul unor funcţii este destul de
dificil de utilizat în cazul în care se doreşte reutilizarea unui număr
mare de funcţii aflate în fişiere obiect separate.
14.3.2 Lucrul cu fişiere bibliotecă
Operaţiile acceptate de fişierele bibliotecă sunt următoarele:
- crearea unei biblioteci;
- adăugarea unuia sau mai multor fişiere obiect la bibliotecă;
- înlocuirea unui fişier obiect cu altul;
- ştergerea unuia sau mai multor fişiere obiect din bibliotecă;
- listarea rutinelor pe care le conţine biblioteca.
În funcţie de compilator, numele programului de bibliotecă şi
opţiunile liniei de comandă pe care programul le acceptă vor diferi. În
continuare prezentăm operaţiile ce pot fi realizate cu funcţiile
bibliotecă utilizând programul TLIB al compilatorului Borland C.
Presupunem că în urma compilării am creat fişierul obiect funcţie.obj
ce conţine o serie de funcţii pe care dorim să le păstrăm într-o
bibliotecă. Crearea unei bilioteci biblioteca.lib care să conţină acest
fişier obiect se realizează cu următoarea linie de comandă:
C:\>tlib biblioteca.lib + functie.obj
După ce fişierul bibliotecă a fost creat, funcţiile acestuia sunt
disponibile pentru compilarea şi legarea noilor programe.
Funcţia de biliotecă TLIB a compilatorului Borland C are
următoarea sintaxă:
tlib cale comandă, fişier
unde:
- cale – este un şir de caractere care specifică calea până la
bilioteca asupra căreia se efectuează operaţia;
- comandă – este formată dintr-un simbol şi numele unui fişier
obiect. Simbol poate fi unul din caracterele: + (adaugă un
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
271
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
BIBLIOGRAFIE
272
6. Marian Gh., Bãdicã C., Pãdeanu L., Limbajul PASCAL,
Indrumar de laborator, Reprografia Universitãţii din Craiova,
1993.
273