Sunteți pe pagina 1din 130

Limbajul de programare 134

6.2. Limbajul de programare C

6.2.1. Vocabularul limbajului. Caractere

La scrierea programelor TURBO C se foloseşte setul de caractere al codului ASCII.


Caracterele din acest set se codifică prin întregi din intervalul [0,127]. Un astfel de întreg
poate fi păstrat în binar pe un octet (8 biţi). Mulţimea caracterelor poate fi împărţită în trei
grupe: caractere negrafice, spaţiu şi caractere grafice.
Spaţiul are codul 32. Caracterele negrafice, exceptând caracterul DEL, care are codul
ASCII 127, au coduri ASCII mai mici decât 32. Caracterele grafice au codurile ASCII mai
mari decât 32. În grupa caracterelor grafice distingem: litere mari, litere mici, cifre, caractere
speciale.
Literele mari şi mici sunt cele ale alfabetului englez. Literele mari au codurile ASCII
în intervalul [65,90]. Valoarea 65 este atribuită literei A, iar 90 literei Z. Celelalte valori
corespund celorlalte litere în aşa fel încât ordinea alfabetică a lor să inducă ordinea crescătoare
a codurilor ASCII.
În mod analog, literele mici au codurile ASCII în intervalul [97,122]. Cifrele 0-9 se
codifică prin codurile ASCII 48-57.
Codul ASCII de valoare zero defineşte caracterul NUL. Acesta este un caracter
impropriu şi spre deosebire de celelalte caractere el nu poate fi generat de la tastatură şi nu are
efect nici la ieşire. El poate fi utilizat pentru a termina un şir arbitrar de caractere deoarece nici
un caracter de la intrare nu poate coincide cu el.
Restul caracterelor negrafice au diferite funcţii. Aşa, de exemplu, codul ASCII 10
realizează deplasarea cursorului în coloana 1 de la linia următoare, iar codul 13 deplasează
cursorul în coloana întâia de pe aceeaşi linie. Caracterul cu codul ASCII 10 se numeşte LF
(Line Feed) iar caracterul cu codul ASCII 13 se numeşte CR (Carriage Return). Cele două
caractere se pot introduce de la tastatură acţionând tasta Enter (numită şi RETURN)
obţinându-se caracterul de rând nou (newline). Acest caracter are şi o notaţie specială în
limbajul C şi anume el se notează prin: \n. Pentru tabulator se utilizează notaţia: \t.
Caracterele: spaţiu, newline, tabulator orizontal le numim caractere sau spaţii albe (white
space).

6.2.1.1. Identificatori (nume)

Un identificator (nume), în limbajul C, este o succesiune de litere şi eventual cifre,


care începe cu o literă. În calitate de litere se folosesc litere mici şi litere mari ale alfabetului
englez, precum şi caracterul de subliniere (_). Literele mici se consideră distincte de cele
135 Limbajul de programare C
mari. Exemple corecte de identificatori: ecuatia_de_grad_doi, x1,x2, a, b, anul_I, _functie, A,
XB. Exemple greşite de identificatori: 1_EM, 3_ut.

6.2.1.2. Cuvinte cheie

Un cuvânt cheie este un cuvânt împrumutat din limba engleză, care are un înţeles
predefinit. Cuvinte cheie se scriu cu litere mici în limbajul C.
Un cuvânt cheie nu poate avea altă utilizare într-un program C decât cea care i-a fost
predefinită. Fiind o succesiune de litere, un cuvânt cheie este totodată şi un nume.
Exemple: if while for break do
Lista cuvintelor cheie este dată în anexa I.
Sensul fiecărui cuvânt cheie va rezulta la definirea construcţiei în care se utilizează.

6.2.1.3. Tipurile de bază din limbajul C

Un program în limbajul C, ca de altfel în orice limbaj de programare, realizează o


succesiune de prelucrări asupra unor date. Datele sunt diferite funţie de natura (tipul) lor.
În limbajul C distingem câteva tipuri predefinite de date, pe care le numim tipuri de
bază. În afara acestor tipuri utilizatorul îşi poate defini tipuri noi funcţie de specificul datelor
problemei pe care o are de rezolvat.
În tabelul 6.1 de mai jos se indică tipurile de bază din limbajul TURBO C.
Tabelul 6.1.

Cuvânt Lungime
Tip de reprezentare internă
cheie în biţi
int 16 întreg binar reprezentat prin complement faţă de 2 pe 2 octeţi
short 16 idem
long 32 întreg binar reprezentat prin complement faţă de 2 pe 4 octeţi
unsigned 16 întreg binar fără semn
char 8 caracter reprezentat prin cod ASCII
float 32 număr reprezentat în virgulă flotantă în simplă precizie
double 64 număr reprezentat în virgulă flotantă în dublă precizie
long double 80 număr reprezentat în virgulă flotantă în dublă precizie

Datele de tip int (întregi cu semn) aparţin intervalului [-32768, 32767]. Datele de tip
long (întregi cu semn în dublă precizie) aparţin intervalului [-231, 331]. Datele de tip unsingned
(întregi fără semn) aparţin intervalului [0,65535]. Datele de tip char au valori în intervalul
[0,255] sau [-128,127]. Se pot utiliza întregi fără semn în dublă lungime folosind succesiunea
de cuvinte cheie: unsigned long. O dată flotantă în simplă precizie de tip float, diferă de zero,
Limbajul de programare C 136
-38 38
are valoarea absolută în intervalul [3.4*10 , 3.4*10 ]. O dată flotantă în dublă precizie de tip
double, long double în intervalul [3.4*10-4932, 1.1*104932].

6.2.1.4. Constante

O contantă are o valoare şi un tip. Atât tipul cât şi valoarea unei constante se definesc
prin caracterele care compun constanta respectivă.

a) Constante întregi

O constantă întreagă poate fi un şir de cifre, care eventual este precedat de un semn. O
constantă întreagă se reprezintă prin complement faţă de 2 pe 16 biţi sau chiar 32 de biţi dacă
nu încape pe 16 biţi. În cazul în care dorim ca o constantă întreagă să fie reprezentată pe 32 de
biţi, chiar dacă ea se poate reprezenta pe 16 biţi, vom termina constanta respectivă prin L sau l.
În felul acesta, se impune tipul long pentru constanta respectivă.
O constantă întreagă, precedată de un zero nesemnificati, se consideră scrisă în
sistemul de numeraţie cu baza 8. De asemenea, o constantă întreagă care începe cu 0x sau 0X
(zero urmat de litera x mică sau mare) se consideră scrisă în sistemul de numeraţie cu baza 16.
În rest se consideră că baza de numeraţie este 10.
Exemple de reprezentări sunt arătate în tabelul 6.2.
Tabelul 6.2.

Repreze Lungimea
Lungime de reprezentare internă
ntare reprezentării
31645 16 biţi întreg zecimal reprezentat în binar prin complement faţă de 2
-12345 16 biţi întreg zecimal reprezentat în binar prin complement faţă de 2
12345L 32 biţi întreg zecimal reprezentat în binar prin complement faţă de 2
întreg octal reprezentat în binar
012345 16 biţi
(o cifră octală se reprezintă pe 3 biţi)
întreg hexazecimal reprezentat în binar
0xa12b 16 biţi
(o cifră hexazecimală se reprezintă pe 4 biţi)
923456 32 biţi întreg zecimal reprezentat în binar prin complement faţă de 2

b) Constantă flotantă (reală)

O constantă flotantă este un număr raţional care se compune din:


- un semn care poate şi lipsi în cazul unui număr nenegativ;
- o parte întreagă care poate fi şi vidă;
- o parte fracţionară care poate fi şi vidă;
- un exponent care poate fi şi vid;
137 Limbajul de programare C
Exemple:
7125.34 -49.0 85. 367e-2 .3789 -.128 4.3E20 -.29e+15

c) Constantă caracter

O constantă caracter reprezintă un caracter şi are ca valoare codul ASCII al


caracterului respectiv. O constantă caracter grafic se poate scrie incluzând caracterul
respectiv între caractere apostrof.
Anumite caractere negrafice au notaţii speciale la scrierea cărora se utilizează
caracterul backslash (\). Aşa, de exemplu, am văzut că pentru caracterul tabulator orizontal
folosim notaţia: \t.
O astfel de constantă caracter se va scrie incluzând notaţia respectivă între caractere
apostrof. Deci constanta caracter tabulator orizontal se va scrie: ‘\t’
În mod analog, constanta caracter de rând nou (newline) se va scrie: ‘\n’
Alte constante caracter care au notaţii consacrate sunt prezentate în tabelul 6.3:
Tabelul 6.3
Reprezentare prin constantă
Caracter Valoare
caracter
revenire cu o poziţie (backspace) ‘\b’ 8
retur de car ‘\r’ 13
salt de pagină la imprimantă ‘\f’ 12

Caracterul backslash poate fi folosit pentru a reprezenta însuşi constanta caracter


apostrof sau chiar backslash. Astfel:
‘\’’ - reprezintă constanta caracter apostrof;
‘\\’ - reprezintă constanta backslash.

d) Constantă şir sau şir de caractere

O constantă şir este o succesiune de zero sau mai multe caractere delimitate prin
ghilimele. Ghilimelele nu fac parte din şirul de caractere. Dacă dorim să folosim caractere
negrafice în compunerea unui şir de caractere, atunci putem folosi convenţia de utilizare a
caracterului backslash indicată mai sus. În particular, dacă într-un şir de caractere dorim să
reprezentăm chiar caracterul ghilimele, atunci vom scrie: \”. De asemenea, însuşi caracterul
backslash se va scrie: \\.

6.2.1.5. Variabile simple

Prin variabilă înţelegem o dată a cărei valoare se poate schimba în timpul executării
programului care o conţine.
Limbajul de programare C 138
Unei variabile i se ataşează un nume prin intermediul căruia putem referi sau chiar
modifica valoarea variabilei respective. Valorile pe care le poate avea o variabilă trebuiesc să
aparţină unui acelaşi tip. De aceea, unei variabile îi corespunde un tip.

Corespondenţa între numele şi tipul unei variabile se realizează printr-o construcţie


specială numită declaraţie.
Sintaxa unei declaraţii în limbajul C este următoarea:
nume_tip lista_de_variabile;
Exemple:
int i,k,x; - i,k şi x sunt variabile de tip int (ele pot avea ca valori întregi binari
reprezentaţi prin complement faţă de 2 pe 16 biţi);
float a,b; - a şi b sunt variabile de tip float (valorile lor sunt reprezentate în
format flotant simplă precizie pe 32 de biţi);
char c; - c este o variabilă de tip char (valorile ei sunt întregi din intervalul
[0,255] sau [-128, 127]).

6.2.1.6. Comentarii

În limbajul C un comentariu începe prin succesiunea de caractere: /* şi se termină


prin: */.
Se pot adăuga comentarii pe o singură linie precedate de o succesiune de două
caractere /.

6.2.2. Expresii
6.2.2.1. Structura expresiilor în limbajul C

O expresie în limbajul C se compune dintr-un operand sau mai mulţi operanzi legaţi
prin operatori.
O expresie are o valoare şi un tip care se determină aplicând operatorii prezenţi
conform priorităţilor şi asociativităţii acestora.
În limbajul C operatorii 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.
Într-o expresie pot fi folosite parantezele rotunde pentru a impune o anumită ordine în
executarea operaţiilor.
Operatorii limbajului C pot fi grupaţi în mai multe clase. Cu toate acestea ei pot fi
utilizaţi împreună într-o aceeaşi expresie.

6.2.2.2. Operatorii aritmetici

Aceştia sunt:
139 Limbajul de programare C
- (minus unar);
+ (plus unar);
* / % operatori binari multiplicativi;
+ - operatori binari aditivi.

Operatorii de pe aceeaşi linie au aceeaşi prioritate. Operatorii unari au prioritate mai


mare decât cei binari. Operatorii multiplicativi au o prioritate mai mare decât cei aditivi.
Operatorul “*” notează operaţia de înmulţire, operatorul “/” operaţia de împărţire, iar
operatorului “%” are ca rezultat restul împărţirii dintre doi operanzi întregi. Operatorii aditivi
notează aceleaşi operaţii ca şi în matematică.

6.2.2.3. Operatori relaţionali

Aceştia sunt:
< (mai mic);
<= (mai mic sau egal);
> (mai mare);
>= (mai mare sau egal).
Toţi operatorii relaţionali au aceeaşi prioritate. Ea este mai mică decât prioritatea
operatorilor aditivi.
Rezultatul aplicării unui operator relaţional este 1 sau 0, după cum operanzii se află în
relaţia definită de operatorul respectiv sau nu.
Exemple:
Fie a = 3 şi b = -8
Atunci:
a>0 are valoarea 1; a<=0 are valoarea 0;
a+b>0 are valoarea 0; a>=0 are valoarea 1;
-a<0 are valoarea 1; a+b>=b-a are valoarea 1;
a+b<=b-a are valoarea 0.

6.2.2.4. Operatori de egalitate

Există doi operatori de egaliate:


== (egal);
!= (diferit).
Operatorii de egalitate au aceeaşi prioritate. Prioritatea lor este imediat mai mică decât
a operatorilor relaţionali.

6.2.2.5. Operatori logici

Operatorii logici sunt:


Limbajul de programare C 140
! (negaţia logică – operator unar);
&& (ŞI logic);
 (SAU logic).
Operatorul “!” are aceeaşi prioritate cu operatorii unari “+” şi “-“. De altfel toţi
operatorii unari au aceeaşi prioritate în limbajul C.

Operatorul “&&” (ŞI logic) este mai prioritar decât operatorul “” (SAU logic), dar
are o prioritate mai mică decât operatorii de egalitate.
În limbajul C nu există valori logice speciale. Valoarea fals se reprezintă prin zero.
Orice valoare diferită de zero reprezintă valoarea adevărat.
Operatorii logici se evaluează de la stânga la dreapta. Dacă la evaluarea unei expresii
se ajunge într-un punct în care se cunoaşte valoarea întregii expresii, atunci restul expresiei nu
se mai evaluează.
Exemplu:
an%4= = 0 && an% 100! = 0 || an% 400 = = 0.
Variabila an, din expresia de mai sus, are valoarea unui întreg ce reprezintă un an
calendaristic. Expresia are valoarea 1 dacă anul reprezentat prin valoarea variabilei an este
bisect şi zero în caz contrar.
Într-adevăr, un an este bisect dacă are loc una din următoarele condiţii:
a) anul este multiplu de 4 şi nu este multiplu de 100; sau
b) este multiplu de 400.
Condiţia a) se exprimă prin: multiplu de 4: an%4 = = 0
şi: &&
nu e multiplu de 100: an%100! = 0
Condiţia b) se exprimă prin: multiplu de 400: an%400 = = 0
În final cele două condiţii se leagă prin operatorul “||” (SAU) şi în felul acesta se obţine
expresia cerută.

6.2.2.6. Operatori logici pe biţi

Aceştia, în ordine descrescătoare a priorităţii lor sunt:


~ (operator unar; complement faţă de unu)
<< >> (deplasări)
& (ŞI pe biţi)
^ (SAU-EXCLUSIV pe biţi)
| (SAU pe biţi)

Operatorul ~ are ca effect inversarea tuturor biţilor: biţii care iniţial au avut valoarea 1
vor deveni 0, iar cei care au avut valoarea 0 vor deveni 1.
Operatorul << este un operator binar pentru deplasare la stânga care se aplică astfel:
141 Limbajul de programare C
a << b va avea ca efect deplasarea la stânga a biţilor lui a cu b poziţii.
Operatorul >> este un operator binar pentru deplasare la dreapta care se aplică astfel:
a >> b va avea ca efect deplasarea la dreapta a biţilor lui a cu b poziţii.
Exemple de utilizare a operatorilor pe biţi:
Considerăm declaraţia: int x=6, y=3;

unde reprezentările interne ale celor două numere întregi sunt următoarele:
x 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0

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

1. Aplicând operatorul ~ numărului x se va obţine:

~x 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 1

care are valoarea în hexazecimal fff9 iar în zecimal are valoarea –7.
2. Aplicând operatorul & celor două numere întregi x, y se va obţine expresia:
x&y 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0

care în hexazecimal şi în zecimal are valoarea 2.


3. Aplicând operatorul | celor două numere întregi x şi y se va obţine valoarea:

x|y 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1

care în hexazecimal şi în zecimal are valoarea 7.


4. Aplicând operatorul << celor două numere întregi x şi y se obţine valoarea:

x<<y 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0
care în hexazecimal are valoarea 30 iar în zecimal are valoarea 48.
5. Aplicând operatorul >> celor două numere întregi x şi y se obţine valoarea:

x>>y 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
care are valoarea 0.
Aplicarea operatorului de deplasare la stânga, << , este echivalentă cu o înmulţire a lui
x cu 2 , iar a operatorului de deplasare la dreapta, >>, cu o împărţire a lui x cu 2y.
y

Observaţie: Operatorii logici pe biţi sunt diferiţi de operatorii logici. De exemplu


pentru valorile lui x şi y anterioare, expresia x && y are ca valoare 1, spre deosebire de x&y
care are valoarea 2, conform exemplului anterior.
Operatorii pe biţi sunt utilizaţi pentru construirea de diverse măşti. De exemplu, într-o
paletă de 16 culori un octet poate păstra două culori. Extragerea culorilor se poate face cu
ajutorul operatorilor logici pe biţi, ca în exemplul din figura următoare:

X= 0 1 1 0 0 1 0 1

C1 C2
Limbajul de programare C 142

C1 = x >> 4, iar
C2 = x & 0x0f

6.2.2.7. Operatorul de atribuire

Operatorul de atribuire în forma cea mai simplă se notează prin caracterul “=”. El se
utilizează în construcţii de forma:
v = expresie.
Operatorul de atribuire are prioritatea cea mai mică faţă de operatorii indicaţi până în
momentul de faţă.
Construcţia v = expresie se numeşte expresie de atribuire. Ea este considerată ca fiind
un caz particular de expresie. Ca orice expresie, ea are o valoare şi un tip. Tipul ei coincide cu
tipul lui v, iar valoarea întregii expresii este chiar valoarea atribuită lui v. Rezultă de aici că o
expresie de forma v1 = (v = expresie) este şi ea legală.
Operatorii de atribuire se asociază de la dreapta la stânga. În general, putem realiza
atribuiri multiple printr-o expresie de forma:
vn = … = v1 = v = expresie.
În evaluarea unei expresii se aplică regula conversiilor implicite. Prin aplicarea ei
rezultă fiecare expresie de tip. În cazul unei expresii de atribuire, dacă expresia din dreapta
semnului egal are un tip diferit de cel al variabilei v, atunci întâi valoarea ei se converteşte spre
tipul variabilei v şi apoi se realizează atribuirea.
Pentru operaţia de atribuire, în afara semnului egal se mai poate folosi şi succesiunea:
op = unde prin op se înţelege unul dintre operatorii binari aritmetici sau logici pe biţi, adică
unul din următorii: % / * - + & ^ | << >>.
Aceşti operatori se folosesc pentru a prescurta expresiile de atribuire. Astfel expresia:
v op= expresie este identică cu expresia de atribuire: v = v op (expresie).
Exemple:
Fie declaraţiile: int i,j,k; double x,y;
x = 3.8 - se atribuie lui x valoarea 3,8;
i = 3.8 - se atribuie lui i valoarea 3;
i = x>0 - se atribuie lui i valoarea 1 dacă x>0 şi 0 în caz contrar;
x = (i*j+k)/(i*j-k)
x* = 3 - este echivalent cu x = x*3;
i<<=10 - este echivalent cu i = i<<10;
x*=y-10 - este echivalent cu x=x*(y-10)
i& = j>>k - este echivalent cu i=i&( j>>k).
143 Limbajul de programare C
Construcţiile de mai jos sunt eronate:
x%=y - operatorul % nu se poate aplica la operanzi care nu sunt
întregi.
i∼ =j - construcţia op= implică un operator binar;
x=i+(j*(k-y)+2.3 - lipseşte o paranteză închisă;
y=3(x-10) - lipseşte operatorul * după 3.

6.2.2.8. Operatori de incrementare şi decrementare

Aceşti operatori sunt unari şi deci au aceeaşi prioritate cu ceilalţi operatori unari din
limbajul C. Operatorul de incrementare se notează prin “++”, iar cel de decrementare prin
“--“.
Operatorul de incrementare măreşte valoarea operandului său cu unu, iar cel de
decrementare micşorează valoarea operandului cu unu. Operatorii pot fi astfel :
prefixaţi: ++ operand -- operand
postfixaţi: operand ++ operand --
În cazul în care sunt folosiţi prefixaţi, ei se aplică întâi şi apoi se foloseşte valoarea
operandului la care s-a aplicat. Când sunt postfixaţi, se foloseşte valoarea operandului
nemodificată de operatorul respectiv, apoi se aplică.
Exemple:
Dacă x are valoarea 3, atunci în atribuirea y= ++x lui y i se atribuie valoarea 4 deoarece
întâi s-a incrementat x şi apoi s-a folosit valoarea x. În schimb, dacă folosim atribuirea y=x++,
pentru aceeaşi valoare a lui x, atunci lui y i se atribuie valoarea 3. În ambele cazuri x se
măreşte cu 1. Diferenţa constă numai în valoarea atribuită: în primul caz (operator prefixat),
se atribuie valoarea incrementată, iar în al doilea caz (operator postfixat), se atribuie valoarea
neincrementată.
Menţionăm că aceşti operatori se pot aplica numai la următorii operanzi: variabilă
simplă cu indici, referire la elementul unei structuri (structurile vor fi definite mai târziu).
Exemple:
int i,j; double x,y;
j=i++ - este echivalent cu atribuirile j=i; i=i+1;
y= -- x - este echivalent cu secvenţa de atribuire: x=x-1; y=x;
x=tab[3]-- - este echivalent cu: x= tab[3]; tab[3]=tab[3]-1;
i=++tab[j+1] - este echivalent cu: tab[j+1]=tab[j+1]+1; i=tab[j+1];
x=tab[++j] - este echivalent cu: j=j+1; x= tab[j];
x = tab[i--] - este echivalent cu: x=tab[i]; i=i-1;
y=++i-j - este echivalent cu: i=i+1;y=i-j;
y=i++-j - este echivalent cu: y=i-j; i=i+1;
Construcţiile următoare sunt eronate:
y=(i-j)++ - operatorul ++ nu se poate aplica expresiei i-j;
Limbajul de programare C 144
x= --(i-j) - operatorul – nu se poate aplica expresiei i-j;
i=j- - - “- -“ nu mai reprezintă operatorul de decrementare.

6.2.2.9. Operatorul de conversie explicită (operatorul cast)

Adesea dorim să forţăm tipul unui operand sau chiar al unei expresii. Acest lucru este
posibil folosind o construcţie de forma: (tip) operand.

Prin aceasta, valoarea operandului se converteşte spre tipul indicat în paranteze.


Exemplu:
int x,y; double z;
Fie x = 10 ş i y = 4, valorile lui x şi respectiv y. Atunci în urma atribuirii: z = x/y, z
primeşte valoarea 2 deoarece 10/4 dă câtul 2 (împărţirea dintre operanzi întregi are ca
rezultat câtul întreg). În schimb, dacă vom converti cei doi operanzi spre tipul double,
atunci împărţirea nu mai este întreagă şi rezultatul va fi 2,5. O astfel de conversie are
forma:
z = (double)x/y
În expresiile de mai sus, construcţia (tip) este un operator unar prin care se explicitează
conversia dorită. El are aceeaşi prioritate ca restul operatorilor unari, iar expresia
(tip)operand se numeşte expresie cast.

6.2.2.10. Operatorul dimensiune (sizeof)

Lungimea în octeţi a unei date se poate determină folosind o construcţie de forma:


sizeof(data) unde data este numele unei variabile simple, al unui tablou, al unei structuri, al
unui tip sau referirea la elementul unui tablou sau structură. Sizeof este operatorul unar
dimensiune şi are aceeaşi prioritate ca ceilalţi operatori unari. În expresia de mai sus,
parantezele rotunde sunt obligatorii numai în cazul în care data este numele unui tip.
Exemple:
int i; float x; double d; char c; int tab[10]; double dtab[10];
sizeof(i) sau sizeof i - are valoarea 2;
sizeof(x) sau sizeof x - are valoarea 4;
sizeof(float) - are valoarea 4;
sizeof(d) sau sizeof d - are valoarea 8;
sizeof(double) - are valoarea 8;
sizeof(c) sau sizeof c - are valoarea 1;
sizeof(tab[i]) sau sizeof tab[i] - are valoarea 2;
sizeof(tab) sau sizeof tab - are valoarea 20;
sizeof(dtab) sau sizeof dtab - are valoarea 80.

6.2.2.11. Regula conversiilor implicite


145 Limbajul de programare C

O expresie poate conţine operanzi de tipuri diferite. Nu există restricţii în acest sens.
Astfel, de exemplu într-o expresie se pot folosi operanzi de tip char. Aceştia se convertesc în
mod automat spre tipul int, înainte de a face operaţii cu ei.
Dacă operanzii unui operator binar sunt de acelaşi tip, se aplică operatorul asupra
operanzilor respectivi, iar tipul rezultatului este acelaşi cu al operanzilor. În caz contrar
sunt necesare conversii care se execută în mod automat, conform regulii de mai jos
(regula conversiilor implicite):

1.Fiecare operand de tip char se converteşte spre tipul int;


2.Dacă unul dintre operanzi este de tip long double, atunci celălalt operand se
converteşte spre tipul long double şi rezultatul va avea tipul long double;
3.Dacă unul dintre operanzi este de tip double, atunci celălalt operand se converteşte
spre tipul double şi rezultatul va avea tipul double;
4.Dacă unul dintre operanzi este de tip float, atunci celălalt operand se converteşte spre
tipul float şi rezultatul va avea tipul float;
5.Dacă unul dintre operanzi este de tip long, atunci celălalt operand se converteşte spre
tipul long şi rezultatul va avea tipul long;
6.Dacă unul dintre operanzi este de tip unsigned, atunci celălalt operand se converteşte
spre tipul unsigned şi rezultatul va avea tipul unsigned;
7.Dacă unul dintre operanzi este de tip int, atunci celălalt operand se converteşte spre
tipul int şi rezultatul va avea tipul int;
Exemple:
int i,j,k; float a,b; double x,y;
unsigned p; long r; char c;
Valorile diferitelor expresii sunt prezentate în tabelul 6.3.
Tabelul 6.3.
Expresii Conversii Tipul expresiei
i+j/k - int
a/b - float
x+y - double
i+a i spre float float
i-3.5 i spre double double
i+5 - int
i+32767 - int
i+x i spre double double
i-c c spre int int
x+100 100 spre double double
p-20 20 spre unsingned unsigned
r*5 5 spre long long
i*31 - int
(long)i*3 i spre long, 3 spre long long
(double)i/j i spre double, j spre double double
Limbajul de programare C 146
se realizează împărţirea întreagă
(double)(i/j) între i şi j şi rezultatul se double
converteşte spre double.

6.2.2.12. Operatori condiţionali

Operatorii condiţionali se utilizează în evaluări de expresii care prezintă alternative.


O astfel de expresie are formatul: exp1? exp2:exp3 unde exp1, exp2 şi exp3 sunt
expresii.
O astfel de expresie se evaluează în felul următor:
1.Se evaluează expresia exp1;
2.Dacă exp1 este diferită de zero, atunci valoarea şi tipul expresiei condiţionale este
egală cu expresia exp2, altfel cu expresia exp3.
Operatorii condiţionali sunt “?” şi “:”. Ei trebuie să fie folosiţi împreună, adică operatorul
“?” Trebuie să aibă un corespondent “:” şi numai unul.
Ei au prioritatea imediat mai mică decât operatorul logic SAU (||) şi imediat mai mare
decât operatorii de atribuire.
Exemple:
1. y?x/y:x*x
În acest exemplu, exp1 este y, exp2 este x/y iar exp3 este x*x.
Expresia se evaluează astfel: dacă y este diferit de zero atunci rezultatul este valoarea
şi tipul expresiei x/y, altfel este valoarea şi tipul expresiei x*x.
O expresie condiţională este un caz particular de expresie şi deci este legală şi
următoarea expresie de atribuire: z=y?x/y:x*x.
Această expresie exprimă un proces de calcul pe care îl putem descrie în pseudocod,
astfel:
dacă y≠ 0 atunci z ← x/y
altfel z ← x*x

2. Determinarea maximului dintre două numere a şi b.


dacă a>b atunci max ← a
altfel max ← b

Expresia corespunzătoare din limbajul C va fi:


max = (a > b)?a : b
În acest exemplu: exp1 este a > b, exp2 este a, iar exp3 este b.

6.2.2.13. Operatorul virgulă


147 Limbajul de programare C

Există cazuri în care este util să grupăm mai multe expresii într-una singură, expresii
care să se evalueze succesiv.
În acest scop se foloseşte operatorul virgulă, care separă secvenţa de expresii, acestea
grupându-se într-o singură expresie.

Operatorul virgulă are cea mai mică prioritate dintre toţi operatorii limbajului C.
Prioritatea lui este imediat mai mică decât a operatorilor de atribuire.
Cu ajutorul lui construim expresii de forma: exp1, exp2,…,expn .
Această expresie, ca oricare alta, are o valoare şi un tip. Atât valoarea, cât şi tipul
întregii expresii coincide cu valoarea şi tipul lui expn deci cu a ultimei expresii.
Exemple:
++i,--j - i se măreşte cu o unitate, apoi j se micşorează cu o
unitate; valoarea şi tipul întregii expresii coincid cu ale lui j;
k=(x=10,y=2*i-5,z=3*j,i+j) - se execută pe rând cele 3 atribuiri, apoi se efectuează
suma i+j care se atribuie lui k.
Amintim că toţi operatorii se asociază de la stânga la dreapta, exceptând cei unari,
condiţionali şi de atribuire care se asociază de la dreapta la stânga. În tabelul 6.4 sunt
prezentate priorităţile operatorilor.
Tabelul 6.4
( ) [ ] .
-(unar) +(unar) *(unar) &(unar) ! ∼ ++ -- (tip) sizeof
* / %
+ -
<< >>
< <= >= >
== !=
&
^

&&

? :
= op= op poate fi: (binar) / % +(binar) -(binar) >> << & ^ !
,
Limbajul de programare C 148
6.3. Structura programelor C

6.3.1. Structura unui program în limbajul C

Un program în limbajul C se compune din una sau mai multe funcţii. Dintre acestea
una este funcţia principală. Funcţia principală defineşte adresa de lansare a programului. Un
program în C se lansează cu prima instrucţiune a funcţiei principale.
Fiecare funcţie are un nume. Funcţia principală are numele main.

6.3.2. Structura unei funcţii

În limbajul C există două feluri de funcţii: funcţii care returnează o valoare şi funcţii
care nu returnează o valoare la revenirea din ele. Structura unei funcţii este următoarea:
tip nume (lista parametrilor formali)
{
declaraţii de variabile locale
instrucţiuni
}
unde:
- tip defineşte tipul valorii returnate de funcţie şi este un cuvânt cheie pentru tipurile de
bază. Dacă funcţia nu returnează nici o valoare, se poate utiliza cuvântul cheie void. Dacă tip
este absent, se presupune că funcţia sau nu returnează nici o valoare sau returnează o valoare
de tip int. Se recomandă ca utilizatorul să indice totodată tipul deoarece absenţa lui constituie
o sursă potenţială de erori.
- lista_parametrilor_formali este fie vidă, fie conţine declaraţiile parametrilor formali
separate prin virgulă.
Menţionăm că parantezele rotunde sunt prezente chiar şi atunci când lista
parametrilor formali este vidă.

6.4. Preprocesare
Un program în limbajul C poate fi prelucrat înainte de a fi compilat. O astfel de
prelucrare se numeşte preprocesare.
Prin preprocesare se pot realiza:
- includeri de texte;
- definiţii şi apeluri de macrouri simple;
- compilare condiţionată;

6.4.1. Includerea unui fişier sursă


149 Limbajul de programare C
Fişierele sursă pot fi incluse cu ajutorul construcţiei #include. Această construcţie are
una din următoarele formate:
#include “specificator_de_fişier” sau
#include <specificator_de_fişier>
unde:
- specificator_de_fişier trebuie să fie un nume de fişier valid din punct de vedere al
sistemului de operare DOS, care poate avea o extensie (“.h”, “.c” etc.) şi opţional o
cale. Diferenţa dintre cele două formate constă în algoritmul de căutare al
fişierului care se include.

Astfel, varianta <...> specifică includerea unui fişier standard, care este căutat de
obicei în directorul INCLUDE.
În cazul în care se utilizează caracterele ghilimele, utilizatorul furnizează fişierul care
se include. Acest fişier va fi căutat în directorul curent sau într-un director precizat.
Un fişier standard care trebuie inclus frecvent este fişierul stdio.h. Includerile de
fişiere, de obicei, se fac la începutul fişierului sursă. În felul acesta datele conţinute în el se pot
utiliza în tot fişierul sursă. De aceea, la începutul fişierelor sursă vom întâlni mai multe
includeri de fişiere, printre care de obicei se va afla şi fişierul stdio.h: #include <stdio.h>.
Un exemplu de închidere a unui fişier utilizator este: #include “mouse.h”.

6.4.2. Constante simbolice

O altă construcţie tratată de procesor este construcţia define. Un format simplu al


acestei construcţii este următorul:
#define nume succesiune_de_caractere.
O constantă simbolică este definită din punctul apariţiei construcţiei define
corespunzătoare ei şi până la sfârşitul fişierului sursă respectiv sau până la redefinirea sau
anihilarea ei prin intermediul construcţiei #undef.
Exemplu: #define A 100

6.5. Intrări/ieşiri standard

Operaţiile de intrare/ieşire se realizează prin apeluri de funcţii. Datele de


intrare/ieşire se presupune că sunt organizate în fişiere.
Unui program BORLAND C i se ataşează în mod automat următoarele fişiere:
- stdin - intrare standard;
- stdout - ieşire standard;
- stderr - ieşire standard pentru erori;
- stdprn - ieşire standard pentru imprimantă;
- stdaux - intrare/ieşire serială.
Limbajul de programare C 150
6.5.1. Funcţia standard printf

Funcţia de bibliotecă printf realizează ieşiri cu format. Sintaxa funcţiei este:


printf(control, par1, par2,…parn),
unde control este un şir de caractere care conţine texte de scris, specificatori de format pentru
datele care se scriu, iar par1, par2,…parn sunt expresii ale căror valori se scriu conform
specificatorilor de format prezentaţi în parametrul de control.
Un specificator de format începe cu un caracter procent (%). În continuare, într-un
specificator de format mai putem avea:

1.Un caracter “-“ opţional: implicit, datele se aliniază în dreapta câmpului în care se
scriu. Atunci când caracterul “-“ este prezent, data corespunzătoare este încadrată la stânga.
2.Un şir de cifre zecimale opţionale, care defineşte dimensiunea minimă a câmpului
afectat datei respective.
3.Un punct opţional, urmat de un şir de cifre zecimal. Şirul de cifre zecimal aflat după
punct defineşte precizia datei care se scrie sub controlul specificatorului respectiv.
4.Una sau două litere, care definesc tipul de conversie aplicat datei care se scrie. În
cazul în care specificatorul de format conţine două litere, prima poate fi l.
În tabelul 6.5 sunt prezentate intrările/ieşirile standard utilizate în limbajul C.
Tabelul 6.5.
Litera Conversia realizată
data se converteşte din tipul int şi zecimal şi se scriu la ieşire caracterele zecimale
d
ale ei, eventual precedate de semnul “-“, dacă este negativă.
o data se converteşte din tipul int în octal şi se scriu la ieşire caracterele ei octale;
data se converteşte din tipul int în hexazecimal şi se scriu la ieşire caracterele ei
x
hexazecimale; cifrele peste 9 se scriu cu literele mici (a-f);
X ca şi în cazul literei x, dar se vor folosi literele mari (A-F);
u data se converteşte din tipul usingned în zecimal întreg fără semn;
valoarea parametrului care îi corespunde se consideră că reprezintă codul ASCII al
c
unui caracter şi se scrie caracterul respectiv;
parametrul care-i corespunde se scrie ca un şir de caractere; se consideră că şirul se
s
termină la întâlnirea caracterului NUL (‘\0’);
valoarea parametrului care-i corespunde se converteşte din tip float sau double în
formatul: dd ...d.dd…d (d reprezintă o cifră zecimală), unde numărul de cifre după
f
punctul zecimal este fie cel indicat de precizie specificatorului de format, fie este
egal cu 6; partea întreagă este precedată de semnul minus dacă numărul este negativ;
conversia se realizează de tipul float sau double în formatul:
d.dd..de± ddd unde numărul cifrelor de după punctul zecimal este dat de precizia
e
specificatorului de format sau este egal cu 6, dacă acesta este absent; partea întreagă
este precedată de minus dacă numărul este negativ;
151 Limbajul de programare C
ca şi în cazul literei e cu deosebirea că litera e se schimbă cu litera E:
E d.dd..dE± ddd în ambele cazuri, la exponent se va scrie una, două sau trei cifre, în
funcţie de valoarea numărului;
se aplică una din conversiile definite de literele f sau e, alegându-se aceea care se
g reprezintă pe un număr minim de caractere; de asemenea, zerourile de la sfârşitul
părţii fracţionare se omit;
G ca şi g cu singura deosebire că se utilizează E în loc de e.

Funcţia printf returnează lungimea totală în octeţi a datelor scrise la terminal sau
valoarea simbolică EOF în caz de eroare. EOF este o constantă simbolică definită în fişierul
stdio.h. Ea este definită astfel: #define EOF – 1.
Exemple:
Fie declaraţiile: int i,j; float a; double x; int c; long k;
i = 558 a = 47.389 x = -123.5e20 c = ‘x’ j = -123 k = 45678
Mai jos apelăm funcţia printf cu diferiţi specificatori de format care permit afişarea la
terminal a valorilor acestor variabile. În tabelul 6.6 sunt prezentate rezultatele afişate în cazul
apelării funcţiei printf:
Tabelul 6.6.

Nr.crt. Apelul funcţiei printf Afişări


1. printf (“i=%d\n”,i); i = 558
2. printf (“i=%2d\n”,i); i = 558
3. printf (“i=%5d\n”,i); i = 558
4. printf (“i=%o\n”,i); i = 1056
5. printf (“i=%x\n”,i); i = 22e
6. printf (“j=%d\n”,j); j = -123
7. printf (“a=%.3f\n”,a); a = 47.389
8. printf (“a=%.2f\n”,a); a = 47.39
9. printf (“x=%e\n”,x); x = -1.235000e+22
10. printf (“c=%c,cod = %d\n”,c,c); c = x, cod = 120
11. printf (“k=%ld\n”,k); k =45678

6.5.2. Funcţia standard scanf

Funcţia bibliotecă scanf realizează intrări cu format de la intrarea standard stdin


(intrare de la terminalul de la care s-a lansat programul). Ea poate fi apelată printr-o
instrucţiune de apel de forma:
scanf (control, par1, par2, .., parn);
Ca şi în cazul funcţiei printf, parametrul control este delimitat de ghilimele şi poate
conţine texte şi specificatori de format.
Caracterele albe din parametrul de control sunt neglijate.
- parametrii par1, par2, .., parn definesc zonele receptoare ale datelor citite prin
intermediul funcţiei scanf. Fiecare dintre ei reprezintă adresa unei zone receptoare.
Limbajul de programare C 152
Acest lucru se indică, în limbajul C, printr-o construcţie de forma: &nume care
determină adresa zonei de memorie rezervată variabilei num. Caracterul “&”, din
construcţia de mai sus, reprezintă un operator unar, numit operatorul adresă. El
are aceeaşi prioritate ca şi ceilalţi operatori unari din limbajul C.

Funcţia scanf citeşte toate câmpurile care corespund specificatorilor de format şi


eventual textelor scrise în parametrul de control. În cazul unei erori, citirea se întrerupe în
locul în care s-a întâlnit eroarea. La revenirea din ea funcţia scanf returnează numărul
câmpurilor citite corect.
Exemplul 1:
void main () /* citeşte un intreg de 4 cifre şi scrie cifrele
respective precedate fiecare de câte un spaţiu */
{
int cif1, cif2, cif3, cif4;
scanf (“%1d%1d%1d%1d”, &cif1, &cif2, &cif3, &cif4);
printf(“%2d%2d%2d%2d\n”, cif1, cif2, cif3, cif4);
}

Exemplul 2:
Programul următor citeşte o dată calendaristică scrisă sub forma: zzllaaaa
zz - ziua cu 2 cifre;
ll - luna cu 2 cifre;
aaaa - anul cu 4 cifre.
apoi se scrie data respectivă permutând anul cu ziua, astfel încât dacă la intrare se citeşte, spre
exemplu, 01031992, la ieşire se va obţine 19920301.

void main () /* citeşte zzllaaaa şi rescrie aaaallzz */


{
int ziua, luna, anul;
scanf (“%2d%2d%4d”, &ziua, &luna, &anul);
printf(“%4d%02d%02d\n”, anul, luna, ziua);
}

Exemplul 3:
#define MAX 50
void main () /* citeşte nume, prenume şi data naşterii şi rescrie datele respective astfel:
linia 1: nume prenume
linia 2: zi luna an */
{ int ziua, luna, an;
char nume [MAX+1], prenume [MAX+1];
scanf (“%50s%50s%d%d%d”, nume, prenume, &zi, &luna, &an);
153 Limbajul de programare C
printf(“%s%s\n”, nume, prenume);
printf(“%d %d %d\n”, zi, luna, an);
}
Exemplu de linie de intrare: Popescu Ion 1 9 1991

6.5.3. Funcţia standard putchar

Funcţia standard putchar se poate utiliza pentru a scrie un caracter în fişierul standard
de ieşire stdout, în poziţia curentă a cursorului.
Exemple:
1) putchar (‘A’);
- se scrie caracterul A în fişierul de ieşire în poziţia curentă a cursorului;
2) putchar (‘A’+10);
- se scrie caracterul de cod ‘A’+10 = 65+10=75, adică litera K;
3) putchar (‘\n’);
- se scrie caracterul de rând nou (newline). Aceasta are ca efect deplasarea
cursorului în coloana 1 din linia următoare.

6.5.4. Funcţia standard getchar

Această funcţie citeşte de la intrarea standard (fişierul standard stdin) caracterul curent
şi returnează codul ASCII al caracterului citit. Tipul valorii returnate este int. La întâlnirea
sfârşitului de fişier (^Z) se returnează valoarea EOF.
Funcţia getchar poate fi apelată printr-o instrucţiune de forma:
getchar()
sau utilizând-o ca operand într-o expresie. Folosind expresia de atribuire:
c =getchar ()
se citeşte caracterul curent de la intrare.

6.5.5. Funcţiile standard getch şi getche

Funcţia getche citeşte de la intrarea standard caracterul curent şi returnează codul


ASCII al caracterului citit. Spre deosebire de getchar, această funcţie are acces direct la
caracter, de îndată ce acesta a fost tastat. Se apelează la fel ca şi getchar, adică fie printr-o
instrucţiune de apel de forma: getche ();
Funcţia getch este similară cu funcţia getche cu singura deosebire că citirea se face
fără ecou (caracterul tastat nu se afişează la terminal şi nici cursorul nu se deplasează). Ea se
apelează în acelaşi mod ca şi funcţia getche.

6.5.6. Funcţiile standard gets şi puts


Limbajul de programare C 154
Pentru a introduce de la terminal un şir de caractere se poate folosi funcţia gets. Ea
permite citirea cu ecou a caracterelor codului ASCII.
Funcţia gets are ca parametru adresa de început a zonei în care se păstrează caracterele
citite. De obicei, această zonă de memorie poate fi zona alocată unui tablou de tip char.

Funcţia gets returnează adresa de început a zonei în care s-au păstrat caracterele sau
zero în cazul în care s-a întâlnit sfârşitul (^Z).
Exemplu:
char t[255]; gets(t);
La acest apel se citesc caracterele tastate pe un rând şi se păstrează în tabloul t.
Caracterul tastat înainte de a acţiona tasta ENTER este urmat în tabloul t de caracterul NUL,
iar la revenirea din funcţie se returnează adresa zonei receptoare adică valoarea lui t.
Funcţia puts realizează operaţia inversă faţă de funcţia gets. Ea afişează la terminal
caracterele şirului de caractere ASCII aflate într-o zonă de memorie. Adresa de început a
acestei zone de memorie este parametrul funcţiei puts.
Exemplu:
char t[255]; gets(t); puts(t);
Se dă funcţia y(x) definită ca mai jos:
3 x 2 + 2 x − 10 pentru x < 0
y( x ) = 
5 x + 2 pentru x ≥ 0
Să se scrie un program care citeşte valoarea variabilei x (virgulă flotantă dublă
precizie) şi scrie valoarea lui y.
Programul este prezentat în continuare:
#include <stdio.h>
#include <conio.h>
void main ()
{ float x,y;
printf("\nIntroduceti valoarea lui y:");
scanf("%f",&x);
y=x<0?3*x*x+2*x-10:5*x+2;
printf("\nValoarea functiei este %f",y);
getch();
}
Pentru citirea şi scrierea datelor de tip float s-a utilizat specificatorul de format %f.

6.6. Instrucţiunile limbajului C


Regula generală este că toate instrucţiunile limbajului C se termină prin punct şi
virgulă, excepţie făcând instrucţiunile care se termină cu acolada închisă (instrucţiunea
compusă şi instrucţiunea switch) după care nu se pune punct şi virgulă.
155 Limbajul de programare C

6.6.1. Instrucţiunea expresie

Instrucţiunea expresie se obţine scriind punct şi virgulă după o expresie.

6.6.2. Instrucţiunea compusă

Instrucţiunea compusă este o succesiune de declaraţii urmate de instrucţiuni,


succesiune inclusă între acolade.
Declaraţiile sau instrucţiunile (eventual ambele) pot lipsi. Această instrucţiune are
formatul:
{ declaraţii
instrucţiuni
}
Observaţii:
1) După paranteza închisă a unei instrucţiuni compuse nu se pune punct şi virgulă.
2) Corpul unei funcţii are aceeaşi structură ca şi instrucţiunea compusă, deci o funcţie
are formatul:
antetul funcţiei
instrucţiune compusă

6.6.3. Instrucţiunea IF
Instrucţiunea if permite să realizeze o ramificare a execuţiei în funcţie de valoarea unei
expresii. Ea are unul din următoarele formate:
Format 1:
if(expresie) instrucţiune 1
Format 2:
if(expresie) instrucţiune 1
else instrucţiune 2
Exemple:
1. Programul următor citeşte trei numere întregi şi afişează maximul dintre ele.
void main () /* citeşte a,b,c şi scrie maximul dintre ele */
{
int a,b,c, max;
scanf (“%d%d%d”, &a, &b, &c);
if (a > b) max = a;
else max = b;
if (c > max) max = c;
printf (“max(%d, %d, %d) = %d\n”, a, b, c, max);
}
2. Programul următor citeşte valoarea lui x şi scrie valoarea funcţiei de mai jos:
Limbajul de programare C 156
4 x 3 + 5 x 2 − 2 x + 1 pentru x < 0

y ( x ) = 100 pentru x = 0
 2
2 x + 8 x − 1 pentru x > 0

void main () /* citeşte x, calculează y(x) şi scrie valoarea lui y */


{
float x,y;
scanf(“%f”, &x);
if(x < 0) y = 4*x*x*x+5*x*x-2*x+1;
else if (x = = 0) y = 100.0;
else y = 2*x*x+8*x-1;
printf(“y(x) = %f\n”, y);
}

6.6.4. Instrucţiunea WHILE

Instrucţiunea while are următorul format:


while (expresie) instrucţiune
Exemple:
1. Programul următor citeşte un şir de numere întregi separate prin caractere albe şi
scrie suma lor.
void main () /* însumează intregii citiţi din fişierul de intrare */
{
int s,i;
s = 0;
while (scanf(“%d”, &i)==1) s = s + i;
printf(“suma = %d\n”, s);
}
Observaţie:
Şirul de la intrare se poate termina cu orice caracter nenumeric (diferit de caracterele
albe) sau chiar prin sfârşitul de fişier (^Z). Reamintim că atât înainte de sfârşitul de fişier, cât
şi după el se acţiona tasta ENTER (RETURN).
2. Programul citeşte un număr întreg n şi scrie n!.
void main () /* citeşte n si scrie n! */
{
int n,i; double f;
f = 1.0; i = 2;
scanf(“%d”, &n);
while (i<=n) {
157 Limbajul de programare C
f = f*i;
i++;}
printf(“n = %d,n! = %g\n”, n,f);
}

Observaţie:
Instrucţiunile din corpul ciclului while:
f = f*i;
i++;
pot fi înlocuite cu una singură: f*=i++; Deci ciclul while se poate scrie mai compact astfel:
while(i<=n) f*=i++;
3. Programul următor citeşte un şir de numere întregi şi scrie maximul dintre ele. Se
presupune că şirul conţine cel puţin un număr.
void main () /* citeşte un şir de numere întregi şi scrie maximul dintre ele */
{
int max,i;
scanf(“%d”, &max);
while (scanf(“%d, &i) = =1) if(i > max) max = i;
printf(“max = %d\n”, max);
}

6.6.5. Instrucţiunea FOR

Instrucţiunea for, ca şi instrucţiunea while, realizează o structură repetitivă


condiţionată anterior. Este folosită pentru repetarea operaţiilor în care se cunoaşte numărul de
paşi şi după o condiţie. Formatul instrucţiunii este:
for(exp1; exp2; exp3) instrucţiune
unde exp1, exp2, exp3 sunt expresii.
Antetul ciclului este definit de: for(exp1; exp2; exp3)
Instrucţiunea care se execută repetat formează corpul ciclului, exp1 constituie partea de
iniţializare a ciclului, exp2 este partea de reiniţializare a lui, iar exp3 reprezintă condiţia de
continuare a ciclului. De obicei, exp1 şi exp3 reprezintă atribuiri.
Instrucţiunea for se execută conform următorilor paşi:
1.Se execută secvenţa de iniţializare definită de expresia exp1.
2.Se evaluează exp2. Dacă exp2 are valoarea zero, atunci se iese din ciclu. Altfel se
execută instrucţiunea din corpul ciclului.
3.Se execută secvenţa de reiniţializare definită de exp3, apoi se reia de la pasul 2.
Echivalenţa dintre instrucţiunile while şi for este sintetizată astfel:
exp1;
while (exp2)
Limbajul de programare C 158
{
instrucţiune
exp3;
}

Un exemplu foarte simplu de ciclu cu pas este însumarea elementelor unui tablou.
s = 0;
for (i=0; i<n; i++) s = s + tab[i];
sau mai compact:
for (s=0, i=0; i<n; i++) s+=tab[i];
În secvenţa compactă s-a introdus expresia s=0 în pasul de iniţializare a ciclului
folosind operatorul virgulă.
Aceeaşi secvenţă se poate scrie folosind instrucţiunea while:
s=0; i=0;
while (i<n)
{
s+=tab[i];i++;
}
sau mai compact:
s = 0; i = 0;
while (i<n) s += tab [i++];
Exemple:

1. Se consideră problema simplă de afişare a numărului de caractere citite de la intrarea


stdin. Vom prezenta două variante de rezolvare, una folosind instrucţiunea while, iar cealaltă
folosind instrucţiunea for.

Varianta cu instrucţiunea while: Varianta cu instrucţiunea for:


#include <stdio.h> #include <stdio.h>
main() main()
/* numara caracterele citite */ /* numara caracterele citite */
{ {
long n; long n;
n = 0; for (n = 0;getchar() ! =EOF) n++);
while (getchar() ! =EOF) n++ printf(“numarul caracterelor citite= %ld\n”, n);
printf(“numarul caracterelor citite= %ld\n”, n); }
}

2. Programul următor citeşte componentele vectorului v scriindu-le câte 10 pe rând,


separate printr-un spaţiu. De asemenea, după ultimul element se scrie caracterul newline.
159 Limbajul de programare C
Deoarece elementele se scriu câte 10 pe rând înseamnă că se va scrie caracterul de
rând nou (newline ) după al 10-lea element, adică după v[9], v[19], v[29],…, precum şi după
ultimul element care este v[n-1]. În rest, se scrie câte un spaţiu după fiecare element.

#define max 100


void main () /*citeşte componentele unui vector şi rescrie câte 10 pe un rând */
{
int n,i; float v[max];
if (scanf (“%d”, &n) !=1 || n <=0 || n>max)
printf(“numar componente eronate \ n”);
else
{ /*se citesc componentele lui v */
for (i=0; i<n; i++); scanf(“%f”, &v[i]);
for (i = 0; i < n; i++) /*se listează componentele lui v*/
printf (“%f%c”, v[i], i%10 = = 9 || i = = n-1? ‘\n’: ‘ ‘);
} /* sfârşit else */
}
3. Programul următor afişează numerele lui Fibonacci mai mici sau egale cu n. Dacă f(0)
= 0 şi f(1) = 1 sunt primele două numere ale lui Fibonacci, atunci următoarele numere ale
lui Fibonacci se obţin folosind relaţia de recurenţă:
f(i) = f(i-2) + f(i-1)

#define MAXFIB 32767


void main () /* generează numerele lui Fibonacci mai mici sau egale cu n*/
{
long n; unsingned f0, f1, fi;
if (scanf (“%1d, &n”) ! = 1  n<0  n > MAXFIB)
printf (“n este eronat sau se depaseste limita MAXFIB \n”);
else
{
printf (“%d\n”, 0);
for (f0=0, f1=1; f1<=n; f0 =f1, f1 =fi)
{
fi = f0 + f1;
printf (“%u\n”, f1);
} /* sfârşit for */
} /* sfârşit else */
}

6.6.6. Instrucţiunea DO-WHILE


Limbajul de programare C 160

Această instrucţiune realizează structura repetitivă condiţionată posterior. Această


structură nu este obligatorie pentru a scrie programe, ea putându-se realiza prin celelalte
structuri. Are următorul format:

do
instrucţiune
while (expresie);
Aceasta, la rândul ei, poate fi scrisă printr-o secvenţă în care se foloseşte instrucţiunea
while:
instrucţiune
while (expresie) instrucţiune
Exemple:
1. Programul următor utilizează o metodă iterativă simplă pentru extragerea rădăcinii
pătrate dintr-un număr. Fie şirul:
x0, x1, ... xn,
unde xn=1/2(xn-1+a/xn-1)
Se poate demonstra că acest şir converge, pentru a>0, către rădăcina pătrată din a.
Convergenţa este rapidă pentru 0<a<=1. În acest caz se poate lua x0 = 1. Pentru a obţine
rădăcina pătrată cu o precizie dată, este suficient ca diferenţa absolută dintre doi termeni
consecutivi ai şirului să fie mai mică decât eroarea admisă EPS. Programul de faţă consideră
EPS = 10-10.
Pentru realizarea procesului iterativ descris de mai sus, sunt suficiente două variabile
x1 şi x2 care păstrează doi termeni consecutivi. Programul în limbajul C este:
#define EPS 1e-10
void main () /* calculeaza rădăcina pătrată dintr-un număr subunitar */
{
double x1, x2, y, a;
if (scanf (“%1f”, &a) !=1 || a<0 || a > 1.0)
printf (“numarul citit nu este subunitar si pozitiv\n”);
else {
x2 = 1.0;
do {
x1 = x2; x2 = 0.5*(x1+a/x1);
if ((y = x2-x1)<0) y=-y;
}
while (y >= EPS);
printf (“radacina patrata din: %g este: %.11f\n”, a, x2);
} /* sfârşit else */
}
161 Limbajul de programare C
6.6.7. Instrucţiunea SWITCH

Instrucţiunea switch permite realizarea structurii selective multiple, fiind folosită


pentru ramificarea programului pe mai multe ramuri.

switch (expresie)
{
case c1: şir1 break;
case c2: şir2 break;
………………………
case cn: şirn break;
default: şir
}
unde: c1, c2, …, cn sunt constante sau constante simbolice;
şir1, şir2, …, şirn sunt şiruri de instrucţiuni;
Exemple:
1. Programul următor citeşte o dată calendaristică de forma: zzllaaaa şi o scrie sub
forma: aaaa, luna zi unde:
zz este ziua pe 2 cifre; aaaa este anul pe 4 cifre;
ll este luna pe 2 cifre; zi este ziua pe 1-2cifre;
luna este denumirea lunii calendaristice;

void main () /* citeşte zzllaaaa şi scrie aaaa, luna zz */


{
int zz,ll,an;
scanf (“%2d%2d%4d”, &zz, &ll, &an);
printf(“%d ”,an);
switch (ll) {
case 1: printf (“Ianuarie”); break;
case 2: printf (“Februarie”); break;
case 3: printf (“Martie”); break;
case 4: printf (“Aprilie”); break;
case 5: printf (“Mai”); break;
case 6: printf (“Iunie”); break;
case 7: printf (“Iulie”); break;
case 8: printf (“August”); break;
case 9: printf (“Septembrie”); break;
case 10: printf (“Octombrie”); break;
case 11: printf (“Decembrie”); break;
case 12: printf (“Noiembrie”); break;
Limbajul de programare C 162
default: printf (“luna eronata”);
}
printf (“\t%d\n”, zz);
}

6.6.8. Instrucţiunea BREAK

Formatul instrucţiunii este următorul:


break;
Aşa cum s-a văzut în paragraful 6.6.7, se poate folosi instrucţiunea break pentru a ieşi
dintr-o instrucţiune switch.
Ea mai poate fi utilizată pentru a ieşi dintr-o instrucţiune ciclică.

6.6.9. Instrucţiunea CONTINUE

Instrucţiunea are următorul format: continue;


Efect:
1.în ciclurile while şi do-while realizează saltul la evaluarea expresiei care decide
asupra continuităţii ciclului;
2.în ciclul for realizează saltul la pasul de reiniţializare.
Un exemplu de program în care se poate utiliza instrucţiunea continue este următorul:
Se citesc de la tastatură temperaturile înregistrate pe durata a n zile. Se cere
programul C care afişează pe ecran media temperaturilor pozitive. Programul va citi toate
temperaturile indiferent dacă sunt pozitive sau negative şi calculează media celor pozitive. În
cazul în care se întâlneşte o temperatură negativă se va ignora.
#include <stdio.h>
#include<conio.h>
void main()
{
int n,i;
float suma_temp,temperatura,temp_medie,nr_temp_pozitive;
puts("\nIntroduceti numarul de zile:");
scanf("%d",&n);
nr_temp_pozitive=suma_temp=0;
for(i=1;i<=n;i++)
{ printf("\nTemperatura pentru ziua %d ",i);
scanf("%f",&temperatura);
if (temperatura<0) continue;
nr_temp_pozitive=nr_temp_pozitive+1;
suma_temp=suma_temp+temperatura;
163 Limbajul de programare C
}
temp_medie=suma_temp/nr_temp_pozitive;
printf("\nTemperatura medie pozitiva este %.2f",temp_medie);
getch();
}

6.6.10. Instrucţiunea GOTO

Instrucţiunea goto are următorul format:


goto nume_de_etichetă;
Ea realizează saltul la instrucţiunea prefixată de eticheta al cărui nume se află scris
după cuvântul cheie goto.
Eticheta trebuie să fie urmată de caracterul “:”.
Un exemplu de program în care se foloseşte instrucţiunea goto este următorul:
Se citesc de la tastatură temperaturile înregistrate pe durata a n zile. Se cere
programul C care afişează numărul zilei în care s-a întâlnit prima temperatură negativă sau un
mesaj în cazul în care nu s-a întâlnit o temperatură negativă.
#include <stdio.h>
#include<conio.h>
void main()
{ int n,i;
float temperatura;
puts("\nIntroduceti numarul de zile:");
scanf("%d",&n);
for(i=1;i<=n;i++)
{ printf("\nTemperatura pentru ziua %d ",i);
scanf("%f",&temperatura);
if (temperatura<0)
{ printf("\nPrima temperatura negativa s-a inregistrat in ziua
%d",i);
goto end; }
}
printf("\nNu s-a inregistrat nici o temperatura negativa ");
end: getch();
}
Observaţie: Se recomandă evitarea folosirii instrucţiunii goto întrucât contravine
regulilor programării structurate; se pot utiliza, în schimb, variabile logice care simulează
funcţionarea instrucţiunii goto.

6.7. Declaraţia de tablou


Limbajul de programare C 164
Un tablou este reprezentat printr-o mulţime finită şi ordonată de elemente de acelaşi tip
care are asociat un nume.
Tablourile se folosesc în cazul aplicaţiilor care lucrează cu mai multe date şi pentru
care declararea unor variabile simple nu mai este suficientă. Un astfel de exemplu ar fi un
program care citeşte temperaturile înregistrate într-o perioadă oarecare de timp şi apoi le
prelucrează (determină temperatura minimă, maximă, le ordonează crescător sau descrescător,
etc.).

În acest caz declararea unei variabile simple cum este variabila “temperatura” din
exemplele anterioare nu mai este suficientă, deoarece ea poate memora la un moment dat doar
temperatura curentă, nu şi pe celelalte temperaturi.
În astfel de situaţii se utilizează date structurate. Datele structurate sunt date care
conţin mai multe date de acelaşi tip sau de tipuri diferite. Tablourile sunt structuri de date care
conţin date de acelaşi tip. Referirea la o dată din cadrul tabloului se face utilizând un indice.
Indicele reprezintă poziţia datei în tablou.
Un tablou, ca orice variabilă simplă, trebuie declarat înainte de a fi utilizat. Declaraţia
de tablou, în forma cea mai simplă, conţine tipul comun, al elementelor sale, numele tabloului
şi limitele superioare pentru fiecare indice, incluse între paranteze drepte:
tip lista_de_elemente;
unde lista_de_elemente se compune dintr-un element sau mai multe, separate prin virgulă. Un
element dintr-o astfel de listă are formatul: nume[lim1][lim2]…[limn] în care lim1, lim2, …,
limn sunt expresii constante care au valori întregi. Prin expresie constantă înţelegem o
expresie care poate fi evaluată la întâlnirea ei de către compilator.
Exemple:
1.Fie v un vector cu 10 componente de tip întreg. El se declară astfel: int v[10];
2.Fie a o matrice cu 100 de linii şi 4 coloane. Elementele ei sunt reprezentate în virgulă
flotantă simplă precizie. Ea se declară astfel: float a[100] [4];
La elementele unui tablou ne referim prin variabile cu indici. O astfel de variabilă se
compune din numele tabloului urmat de unul sau mai mulţi indici, fiecare indice inclus în
paranteze drepte. Numărul indicilor defineşte dimensiunea tabloului. Indicii sunt expresii care
au valori întregi. Limita inferioară a indicilor este zero.
Astfel, dacă v este tabloul declarat în exemplul 1, atunci elementele lui sunt: v[0], v[1],
v[2], v[3], v[4], v[5], v[6], v[7], v[8], v[9].
În cazul matricei a declarate în exemplu 2, elementele ei vor fi
pe prima linie: a[0] [0], a[0] [1], a[0] [2], a[0] [3]
pe a doua linie: a[1] [0], a[1] [1], a[1] [2], a[1] [3]
...
pe ultima linie: a[99] [0], a[99] [1], a[99] [2], a[99] [3]
165 Limbajul de programare C
Numele unui tablou poate fi utilizat în diferite construcţii, el având ca valoare adresa
primului său element.
În figura 6.1 se indică repartizarea memoriei pentru tabloul tab, declarat mai sus:

Adresa lui
tab[0] tab[0] tab[1] tab[2] tab [3]

Fig. 6.1. Repartizarea memoriei pentru tabloul tab.

Exemple de programe care utilizează tipul de date tablou.


1. Se presupune că de-a lungul unei perioade de timp de n zile se înregistrează
temperatura mediului ambiant. Se cere un program C care citeşte de la tastatură aceste
temperaturi şi determină:
a) Temperatura minimă şi maximă precum şi zilele în care acestea s-au înregistrat;
b) Numărul de temperaturi negative;
c) Temperatura medie de-a lungul celor n zile.
#include <stdio.h>
#include<conio.h>
void main()
{ int temperatura[100];
int n,t_max,t_min,nr_t_neg,i;
printf("\nIntroduceti numarul de zile:");
scanf("%d",&n);
for (i=0;i<n;i++)
{ printf("\nTemperatura din ziua %d : ",i+1);
scanf("%d",&temperatura[i]); }
t_max=t_min=temperatura[0];
for (i=0;i<n;i++)
{ if (temperatura[i]<t_min) t_min=temperatura[i];
if (temperatura[i]>t_max) t_max=temperatura[i]; }
printf("\nTemperatura maxima este %d si s-a inregistrat in zilele ",t_max);
for (i=0;i<n;i++)
if (temperatura[i]==t_max)
printf("%d ",i+1);
printf("\nTemperatura minima este %d si s-a inregistrat in zilele ",t_min);
for (i=0;i<n;i++)
if (temperatura[i]==t_min)
printf("%d ",i+1);
nr_t_neg=0;
Limbajul de programare C 166
for(i=0;i<n;i++)
if (temperatura[i]<0)nr_t_neg++;
printf("\nS-au inregistrat %d temperaturi negative",nr_t_neg);
getch();
int s=0;
for (i=0;i<n;i++)
s+=temperatura[i];
printf("\nTemperatura medie este %.2f",(float)s/n); }

2. Se consideră o matrice cu „n” linii şi „m” coloane care are elemente de tip real. Se
cere programul C care determină toate elementele din matrice, împreună cu poziţiile lor, care
au proprietate de a fi maxime pe linia în care se află şi minime pe coloana în care se află.
Programul va conţine şi secvenţa de afişare a matricei date.
Spre exemplu, pentru matricea următoare:
2 14 1 9 11
−8 0 3 6 1
2 −1 3 7 4
6 10 1 11 1
un astfel de element este „6” aflat la intersecţia liniei „2” cu coloana „4”.
Problema poate fi rezolvată în felul următor: Se determină pentru fiecare linie
elementul maxim şi pentru fiecare coloană elementul minim. Aceste valori se păstrează în câte
un vector cu numele „max”, respectiv „min”. După determinarea elementelor acestor doi
vectori, ele se vor compara între ele pentru determinarea elementului căutat. Pentru matricea
din exemplul anterior cei doi vectori vor fi:
 2 14 1 9 11   14 
   
− 8 0 3 6 1 6
 2 −1 ⇒
3 7 4 7 
   
 6 10 1 11 1   11 


(− 8 −1 1 6 1)
Programul este prezentat în continuare:

#include <stdio.h>
#include<conio.h>
void main()
{float a[10][10],min[10],max[10];
int i,j,m,n;
printf("\nIntroduceti nr. de linii:");
scanf("%d",&n);
167 Limbajul de programare C
printf("\nIntroduceti nr. de coloane:");
scanf("%d",&m);
printf("\nIntroduceti elementele matricei\n");
for (i=0;i<n;i++)
for(j=0;j<m;j++)
{ printf("\na[%d][%d]=",i,j);
scanf("%f",&a[i][j]); }
printf("\nMatricea data este:\n");

for (i=0;i<n;i++) //secvenţa de afişare elemente


matrice
{ for (j=0;j<m;j++)
printf("%.2f ",a[i][j]);
printf("\n"); }
for(i=0;i<n;i++) //secvenţa de determinare a maximelor pe
linii
{ max[i]=a[i][0];
for (j=0;j<m;j++)
if (max[i]<a[i][j]) max[i]=a[i][j]; }
for(j=0;j<m;j++) //secvenţa de determinare a maximelor pe
linii
{ min[j]=a[0][j];
for (i=0;i<n;i++)
if (min[j]>a[i][j]) min[j]=a[i][j]; }
int gasit=0; //Secvenţa de comparare element cu element a celor doi vectori
for (i=0;i<n;i++)
for (j=0;j<m;j++)
if (max[i]==min[j])
{ gasit=1;
printf ("\nElementul %.2f de pe linia %d, coloana %d",a[i][j],i,j); }
if (gasit==0) printf("\nNu exista un astfel de element!")
getch();
}

3. Se cere un program care citeşte de la tastatură elementele întregi ale unui vector de
dimensiune „n” şi afişează aceste elemente în ordine crescătoare.
Problema are o importanţă destul de mare în programare, deoarece problema sortării
crescătoare sau descrescătoare a unui şir de valori este destul de frecvent întâlnită în practică.
S-au descoperit un număr mare de algoritmi de sortare, dintre care se va utiliza un algoritm
Limbajul de programare C 168
relativ simplu, algoritm cunoscut sub denumirea de „bubblesort” sau sortare prin metoda
bulelor.
Algoritmul constă din parcurgerea vectorului de mai multe ori, în fiecare parcurgere
comparându-se elementele aflate pe poziţii alăturate. Ori de câte ori cele două elemente
comparate nu se află în ordinea dorită, ele se vor interschimba. Pentru interschimbare se va
folosi o variabilă auxiliară „aux”. Algoritmul se consideră încheiat în urma unei parcurgeri a
vectorului în care nu se mai efectuează nici o interchimbare. Pentru a se marca efectuarea unei
interschimbări se va utiliza o variabilă de tip întreg, „gata”, cu rol de „adevărat”/”fals”.
Această variabilă va primi valoarea „1” înainte de fiecare parcurgere a vectorului şi valoarea
„0” în momentul în care se efectuează o interschimbare. Algoritmul se va considera încheiat
când variabila „gata” va rămâne „1” la sfârşitul parcurgerii vectorului.

Algoritmul este explicat pentru vectorul:


A = [9, 2, 6, 1, 3 ].
a) Prima parcurgere:
gata = 1

i=0 i=1 i=2 i=3


9 2 2 2 2 gata=0
2 9 6 6 6 gata=0
6 6 9 1 1 gata=0
1 1 1 9 3 gata=0
3 3 3 3 9

b) A doua parcurgere:
gata = 1

i=0 i=1 i=2 i=3


2 2 2 2 2
6 6 1 1 1 gata=0
1 1 6 3 3 gata=0
3 3 3 6 6
9 9 9 9 9
169 Limbajul de programare C
c) A doua parcurgere:
gata = 1
i=0 i=1 i=2 i=3
2 1 1 1 1 gata=0
1 2 2 2 2
3 3 3 3 3
6 6 6 6 6
9 9 9 9 9

În cea de-a treia parcurgere, care în acest caz este şi ultima, nu se mai efectuează nici o
interschimbare, prin urmare vectorul este ordonat şi algoritmul se încheie.
Denumirea algoritmului – „Sortare prin metoda bulelor” sau „Bubblesort” – provine
din analogia care se poate face între elementele vectorului şi nişte bule cu densităţi
proporţionale cu valoarea elementului respectiv. Se observă că elementele de valori mari (deci
bulele cu densităţi mai mari) coboară, cum este şi cazul elementului „9” care la început se afla
pe poziţia „0” în vector şi în urma primei parcurgeri a vectorului a coborât pe ultima poziţie.
Programul C care implementează acest algoritm este prezentat în continuare.

#include <stdio.h>
#include<conio.h>
void main()
{ int a[100],n,i,gata,aux;
printf("\nNumar elemente vector:");
scanf("%d",&n);
for (i=0;i<n;i++)
{ printf("\na[%d]=",i);
scanf("%d",&a[i]); }
do
{ gata=1;
for (i=0;i<n-1;i++) //parcurgere vector
if (a[i]>a[i+1])
{ aux=a[i]; //efectuare interschimbare
a[i]=a[i+1];
a[i+1]=aux;
gata=0; }
}
while(gata==0); //test daca s-a terminat sortarea
printf("\nVectorul sortat este:\n");
for (i=0;i<n;i++)
printf("%d ",a[i]);
Limbajul de programare C 170
getch();
}

6.8. Apelul şi revenirea dintr-o funcţie

6.8.1 Apelul unei funcţii

Am amintit în capitolul 1 că funcţiile sunt de două feluri:


- funcţii care returnează o valoare la revenirea din ea;
- funcţii care nu returnează o valoare la revenirea din ea.
O funcţie care nu returnează o valoare la revenirea din ea se apelează printr-o
instrucţiune de apel. Ea are următorul format:
nume(lista_parametrilor_efectivi);
În cazul în care o funcţie returnează o valoare, ea poate fi apelată fie printr-o
instrucţiune de apel, fie sub forma unui operand al unei expresii. Apelul se face printr-o
instrucţiune de apel atunci când nu dorim să utilizăm valoarea returnată de funcţia respectivă.
Definiţia unei funcţii are structura următoare:

Antet
instrucţiune compusă
Antetul are formatul:
tip nume(lista_parametrilor_formali)
Funcţiile care nu returnează o valoare la revenirea din ele au ca tip cuvântul cheie void.
De asemenea, dacă lista_parametrilor_formali este vidă, ea poate fi definită prin cuvântul
cheie void. Lista parametrilor formali conţine declaraţiile acestora separate prin virgulă.
Parametrii formali ai unei funcţii sunt reprezentaţi de datele prin care funcţia respectivă
comunică cu alte funcţii din cadrul programului respectiv. Ea se pune în corespondenţă cu lista
parametrilor efectivi din instrucţiunea de apel. Cele două liste de parametrii trebuie să coincidă
ca număr, ordine şi tip.

6.8.2 Prototipul unei funcţii

O funcţie poate fi apelată dacă ea este definită în fişierul sursă înainte de a fi apelată.
Acest lucru nu este totdeauna posibil şi în astfel de cazuri apelul funcţiei trebuie să fie
precedat de prototipul ei. Prototipul poate fi scris la începutul fişierului sursă şi în acest caz el
va fi valabil în tot fişierul sursă.
Prototipul unei funcţii este de fapt antetul funcţiei şi conţine toate informaţiile
referitoare la funcţia respectivă: numele funcţiei, tipul returnat de funcţie, numărul şi tipul
parametrilor funcţiei.
171 Limbajul de programare C
Exemple:
1) double val(double x, int a); Acest prototip indică faptul că funcţia val returnează o
valoare flotantă în dublă precizie şi are doi parametri, primul de tip double şi al doilea de tip
int.
Exemple de instrucţiuni de apel:
y= val(z,b); s=val(12.6, 5);
În aceste exemple s,z şi y trebuie să fie variabile de tipul „double” iar b o variabilă de
tipul „int”.
Se observă că o funcţie poate fi apelată fie cu parametrii constanţi, fie cu parametrii
variabili.

2) void citire(void);
Conform acestui prototip funcţia citire nu returnează nici o valoare şi nu are
parametrii. Funcţia poate fi apelată printr-o instrucţiune de apel de forma:
citire( );
3) double sum (double x[], int n);
Funcţia sum returnează o valoare flotantă în dublă precizie. Ea are doi parametrii: un
tablou unidimensional de tip double şi o variabilă de tip int. Această funcţie se poate apela
printr-o instrucţiune de apel de forma:

s=sum(a,m);
unde a trebuie să fie un tablou cu elemente de tip „double”, m o variabilă de tipul „int”, iar s o
variabilă de tipul „double”.

6.8.3. Apel prin valoare şi apel prin referinţă

La apelul unei funcţii, fiecărui parametru formal i se atribuie valoarea parametrului


efectiv care-i corespunde. Deci, la apelul unei funcţii, se transferă valorile parametrilor
efectivi. Din această cauză, se spune că apelul este prin valoare.
În anumite limbaje de programare, la apel se transferă nu valorile parametrilor efectivi
ci adresele acestor valori. În acest caz se spune că apelul este prin referinţă.
Între cele două tipuri de apeluri există o diferenţă esenţială şi anume: în cazul apelului
prin valoare, funcţia apelată nu poate modifica parametrii efectivi din funcţia care a făcut
apelul, neavând acces la ei. De fapt funcţia primeşte o copie a valorilor de apel.
În cazul apelului prin referinţă, funcţia apelată dispunând de adresa parametrilor
efectivi, îi poate modifica pe aceştia.
În limbajul C, apelul prin referinţă se realizează în cazul în care parametrul efectiv este
numele unui tablou. În acest caz, se transferă valoarea numelui tabloului, adică adresa
primului său element. În felul acesta, funcţia apelată dispune de adresa de început a tabloului
utilizat ca parametru şi în consecinţă ea poate modifica elementele tabloului respectiv.
Limbajul de programare C 172
În concluzie, în limbajul C, transferul parametrilor se face prin valoare. Acest
transfer devine prin referinţă în cazul în care parametrul efectiv este numele unui tablou sau se
cere explicit acest lucru adăugând caracterul & în faţa parametrului.

6.8.4. Revenirea dintr-o funcţie

Revenirea dintr-o funcţie se poate face în două moduri:


-la întâlnirea instrucţiunii return;
-după execuţia ultimei sale instrucţiuni, adică a instrucţiunii care precede acolada
închisă ce termină corpul funcţiei respective.
Instrucţiunea return are două formate: return;
sau return expresie;
Primul format se utilizează când funcţia nu returnează o valoare, iar cel de al doilea
format se utilizează atunci când funcţia returnează o valoare.
Un exemplu de program care utilizează funcţii definite de utilizatori este următorul:
Se citesc de la tastatură cele n elemente de tip întreg ale unui vector. Se cere
programul C care determină:
a) Suma cifrelor fiecărui element din vector;
b) Inversul fircărui element din vector;
c) Cel mai mare divizor comun al elementelor din vector.

Pentru ca programul să realizeze aceste cerinţe vom defini trei funcţii utilizator:
a) O funcţie “suma_cifre” care va calcula suma cifrelor unui număr întreg transmis
ca parametru;
b) O funcţie “invers” care va returna inversul unui număr întreg transmis ca
parametru;
c) O funcţie “cmmdc” care va determina cel mai mare divizor comun a două numere
întregi transmise ca parametri.
Programul este prezentat în continuare:
#include<stdio.h>
#include<conio.h>
int suma_cifre(int n)
{ int s;
s = 0;
while (n!=0)
{ s = s + n % 10;
n = n / 10; }
return s; }
int invers(int n)
{ int inv;
173 Limbajul de programare C
inv = 0;
while (n!=0)
{ inv = inv * 10 + n % 10;
n = n / 10; }
return inv; }
int cmmdc(int a, int b)
{ while (a!=b)
if (a>b) a = a - b;
else b = b - a;
return a; }
void main()
{ int a[100],n,i,c;
float p;
printf("\nNumar elemente vector:");
scanf("%d",&n);
printf("\nElementele vectorului:\n");
for (i=0;i<n;i++)
{ printf("a[%d]=",i);
scanf("%d",&a[i]); }

printf("\nSuma cifrelor elementelor vectorului:\n");


for (i=0;i<n;i++)
printf("%d ",suma_cifre(a[i]));
printf("\nNumerele inverse elementelor vectorului:\n");
for (i=0;i<n;i++)
printf("%d ",invers(a[i]));
c = a[0];
for (i=1;i<n;i++)
c=cmmdc(c,a[i]);
printf("\nCel mai mare divizor comun este %d",c);
getch(); }

6.9. Clase de memorie

Compilatorul C alocă memorie variabilelor din program de dimensiune


corespunzătoare tipului fiecăreia. Memoria poate fi alocată static sau dinamic. Memoria
alocată dinamic se repartizează pe stivă, spre deosebire de cea statică, repartizată într-o zonă
specială afectată programului. În funcţie de modul în care se alocă memoria, vom distinge mai
multe clase de memorie.
Limbajul de programare C 174
O primă clasă de memorie este aceea alocată variabilelor globale. Lor li se alocă
memorie pe toată perioada execuţiei programului şi ele pot fi utilizate în tot programul.
O altă clasă de memorie este alocată variabilelor locale. O astfel de variabilă nu este
valabilă în tot programul. Ea are o utilizare locală.

6.9.1. Variabile globale

O variabilă globală are o definiţie şi atâtea declaraţii de variabilă externă câte sunt
necesare.
În mod implicit, definiţia unei variabile globale determină ca variabila respectivă să fie
definită începând din punctul scrierii ei şi până la sfârşitul fişierului sursă respectiv. De aceea
se recomandă ca definiţiile variabilelor globale să fie scrise la începutul fişierului sursă.
Exemplu:
int i; double x;
void main ()
{ …………
i = 100;
………
x = i*x;
……… }

Variabilele i şi x, fiind definite în afara funcţiilor programului, sunt globale.


Construcţiile int i şi respectiv double x sunt definiţiile lor, iar din punct de vedere
sintactic ele au acelaşi format ca declaraţii obişnuite.
Ele pot fi folosite în toate funcţiile din fişierul sursă care conţine definiţiile lor fără alte
declaraţii suplimentare.
În schimb, pentru a le utiliza în funcţii ale programului situate în alte fişiere sursă decât
în cel în care sunt definite, ele trebuie declarate ca externe în funcţiile respective.
O declaraţie de variabilă externă este la fel ca şi o declaraţie obişnuită, doar că ea
începe prin cuvântul extern.
De exemplu, dacă dorim să folosim variabile globale i şi x definite mai sus într-o
funcţie f, aflată într-un alt fişier sursă decât cel în care au fost ele definite, atunci în corpul
funcţiei f le vom declara ca externe:
f (…)…
{ extern int i;
extern double x;
………………
x = i*x;
…………… }
175 Limbajul de programare C
Compilatorul alocă memorie pentru variabilele globale în momentul întâlnirii
definiţiilor lor. Această memorie se află într-o zonă specială destinată acestui scop şi ea
rămâne alocată pe întreaga durată a execuţiei programului.

6.9.2. Variabile locale

Variabilele locale nu sunt valabile în tot programul. Exemple de variabile locale sunt
variabilele declarate în corpul unei funcţii şi care nu sunt declarate ca externe. Ele pot fi
folosite numai în corpul funcţiei respective, adică începând din momentul apelării funcţiei
respective şi până la revenirea din ea.
Astfel de variabile pot fi alocate pe stivă. În acest caz, lor li se alocă memorie pe stivă
la intrarea în funcţia în care sunt declarate şi îşi pierd alocarea respectivă la ieşirea din funcţie,
când se reface stiva la forma dinaintea apelului.
Stiva este o zonă de memorie cu o destinaţie specială. Ea are o structură de tip LIFO
(Last Input First Output). Într-o astfel de zonă de memorie se alocă pe rând memorie
variabilelor locale declarate în corpul funcţiilor. Eliberarea memoriei astfel alocate se face în
ordine inversă alocării ei, la terminarea execuţiei funcţiei respective.
Variabilele alocate în acest fel se numesc automatice. Ele se declară în corpul
funcţiilor şi au sintaxa obişnuită.
Se poate ca variabilele locale să nu fie alocate pe stivă, ci într-o zonă de memorie
destinată special în acest scop. O astfel de variabilă se spune că este statică.

O variabilă statică declarată în corpul unei funcţii are domeniul de valabilitate numai
corpul funcţiei respective, ca şi variabilele automatice. Spre deosebire de ele, variabila statică
nu se alocă pe stivă, ci într-o zonă de memorie destinată acestui scop.
În cazul în care o variabilă statică este declarată în afara funcţiilor, ea este definită din
punctul în care a fost declarată şi până la sfârşitul fişierului sursă care conţine declaraţia ei.
Spre deosebire de variabilele globale, o variabilă statică declarată în acest fel nu poate fi
declarată ca externă.
Menţionăm că tablourile de dimensiuni mari se recomandă a fi declarate statice,
deoarece dacă ele sunt automatice, pot să conducă la depăşirea stivei alocate programului.
Amintim că, implicit, dimensiunea stivei alocate unui program este de 4K octeţi.
Exemple:
1. Fişierul fis1.cpp este un fişier sursă care conţine două variabile globale i şi x o
variabilă statică y şi două funcţii main şi f, care şi ele conţin la rândul lor variabilele statice a
şi b.

/* variabile globale
int i; /* definitia variabilei i */
Limbajul de programare C 176
double x; /*definitia variabilelor x
*/
/* variabile statice */
static int y;

void main ()
{ ………………………
static char a; /* variabila statica
*/
int c; /* variabila automatica
*/
……………………….
/* se pot folosi variabilele i, x, y, a si c*/
……………………….
}

f(…)…
{ int p; /* variabila automatica
*/
static float b; /* variabila statica */
…………………..
/* se pot folosi variabilele i, x, y, p si b */
……………………….
}
Variabilele a şi c sunt locale funcţiei main şi ele nu pot fi folosite în funcţia f. La fel,
variabilele p şi b sunt locale în funcţia f şi nu pot fi folosite în funcţia main.

2. Funcţia fis2.cpp conţine funcţiile f1 şi f2 care intră în componenţa aceluiaşi


program ca şi funcţiile main şi f din fişierul fis2.cpp.

/* variabile statice */
static usingned t;
f1 (…)…
{ …………………
extern int i; /* declaratie externa pentru i */
extern double x; /* declaratie externa pentru x */
static int k;
……………….
/* se pot folosi variabilele i, x, k si t */
……………… }
177 Limbajul de programare C
f2(…)…
{ extern int i; /* declaratie externa pentru i */
static double s; /* variabila statica */
………………
/* se pot folosi variabilele i, s, si t */
………………… }
Se observă că variabila statică y, definită în fişierul fis1.cpp, nu poate fi utilizată în
fişierul fis2.cpp. De asemenea, variabila statică t nu poate fi folosită în fişierul fis1.cpp.
Variabila globală x nu poate fi folosită în funcţia f2, ea nefiind declarată ca şi externă.
Observaţii:
1. Variabilele globale constituie un mijloc simplu de interfaţă între funcţiile unui
program.
2. Funcţiile, ca şi variabile globale, pot fi utilizate în tot programul.

6.9.3. Variabile registru

Limbajul C oferă posibilitatea de a aloca variabilele în regiştri. Prin aceasta se poate


economisi atât timp de execuţie, cât şi memorie.
Se pot aloca în regiştri numai parametrii şi variabilele automatice de tip int, char şi
pointer.
O variabilă registru se declară în mod obişnuit, precedând declaraţia ei prin cuvântul
register.
Exemplu:
f(register int x)
{ register char a;
……………… }

Întâi se alocă parametrul x într-un registru şi apoi se alocă a într-un alt registru.
Observaţie: Se recomandă alocarea în regiştri a variabilelor care au o utilizare mare,
ţinând seama însă, că numărul acestora este relativ mic.

6.9.4. Iniţializarea

Variabilelor li se pot atribui valori iniţiale. O variabilă simplă se poate iniţializa


printr-o declaraţie de forma:
tip nume = expresie;
sau static tip nume = expresie;
dacă variabila este statică.
Variabilelor globale şi statice li se atribuie valorile iniţiale la lansarea programului.
Expresia utilizată în cazul acestor variabile trebuie să fie expresie constantă care să poată fi
Limbajul de programare C 178
evaluată de compilator la întâlnirea ei, deoarece variabilele globale şi statice se iniţializează
prin valori definite la compilare.
Variabilele automatice se iniţializează la execuţie, de fiecare dată când se activează
funcţia în care sunt declarate. Din această cauză, nu mai este necesar ca expresia să fie o
expresie constantă. Totuşi, la întâlnirea ei, trebuie să fie definiţi operanzii expresie de
iniţializare.
Exemplu:
f (int n) ;
{ int x = 10;
int y = x*n;
…………… }
La întâlnirea expresiei x*n sunt deja definiţi ambii operanzi:
- x a fost în prealabil iniţializat;
- n are o valoare care a fost deja transferată la apel.
Variabilele globale şi statice neiniţializate au implicit valoarea egală cu zero.
Variabilele automatice neiniţializate au o valoare iniţială imprevizibilă.
Tablourile se pot iniţializa printr-o listă de expresii incluse între acolade. Astfel, un
tablou unidimensional se poate iniţializa folosind următorul format:
tip nume [n] = {exp1, exp2, …, expn}; sau
static tip nume [n] = {exp1, exp2, …, expn}; dacă tabloul este static
Pentru un tablou bidimensional vom folosi următorul format:
tip nume [n] = {
{exp11, exp12, …, exp1m},
{exp21, exp22, …, exp2m},
……………………………...............
{expn1, expn2, …, expnm},
};

Exemple:
int it[ ] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int matrice [4] [3] = {{-1, 0, 1}, {-1}, {0, 1}, {0, 0, 1}}.
Observaţii:
1. În cazul vectorului it nu s-a specificat la declarare dimensiunea acestuia deoarece
ea rezultă în urma iniţializării, fiind egală cu 9;
2. La iniţializarea elementelor tabloului matrice o parte din elemente pot rămâne
neiniţializate.

6.10. Pointeri în limbajul C

Un pointer este o variabilă care are ca valori adrese.


179 Limbajul de programare C
Pointerii se utilizează pentru a face referire la date cunoscute prin adresele lor. Astfel,
dacă p este o variabilă de tip care are ca valoare adresa zonei de memorie alocată pentru
variabila întreagă x, atunci: ∗p reprezintă valoarea variabilei x, ca în figura 6.2:

… …

Memoria x p (adresa de memorie Memoria 5


2500
a variabilei x)

… …

Fig. 6.2. Referirea la o variabilă utilizând pointeri.

În figura 6.2 valoarea “2500” reprezintă adresa de memorie a valorii “5”, adică este un
pointer.
Operatorul ∗ (unar) are aceeaşi prioritate ca şi ceilalţi operatori unari din limbajul C.
Dacă p conţine adresa zonei de memorie alocată variabilei x, vom spune că p
pointează spre x sau că p conţine adresa lui x.
Pentru a atribui unui pointer adresa unei variabile, putem folosi operatorul unar &.
Astfel, dacă dorim ca p să pointeze spre x, putem utiliza atribuirea: p = &x;

6.10.1. Declaraţia de pointer

În general, un pointer se declară prin:


tip ∗nume;
ceea ce înseamnă că nume este un pointer care pointează spre o zonă de memorie ce conţine o
dată de tipul tip.

Comparând declaraţia anterioară de pointer cu una obişnuită:


tip nume;
putem considera că tip∗ dintr-o declaraţie pointer reprezintă tip dintr-o declaraţie obişnuită.
De aceea, construcţia tip∗ se spune că reprezintă un tip nou, tipul pointer.
Există cazuri în care dorim ca un pointer să fie utilizat cu mai multe tipuri de date. În
acest caz, la declararea lui nu dorim să specificăm un tip anume. Aceasta se realizează folosind
cuvântul cheie void astfel:
void ∗nume;
Exemple:
Limbajul de programare C 180
Fie declaraţiile:
int x,y;
int ∗p;
1. y = x+100; este echivalentă cu secvenţa:
p = &x;
y = ∗p+100;
în care lui y i se atribuie suma dintre conţinutul zonei a cărei adresă se află în p şi 100.
2. x = y; este echivalentă cu secvenţa:
p = &x;
∗p = y;
în care conţinutul zonei a cărei adresă se află în p devine egal cu valoarea lui y.
3. x++; este echivalentă cu secvenţa:
p = &x;
(∗p)++;
în care conţinutul zonei a cărei adresă se află în p se măreşte cu 1.
4. Fie funcţia perm de mai jos:
void perm(int x,int y)
{ int temp;
temp=x;
x=y;
y=temp; }
Un apel de forma:
perm (a,b);
nu are nici un efect, deoarece la acest apel valoarea lui a se transferă parametrului x, iar a lui b
parametrului y. Apoi funcţia perm permută valorile parametrilor x şi y, însă prin aceasta
valorile parametrilor efectivi a şi b rămân nemodificate. Deci la revenirea din funcţia perm,
variabilele a şi b au aceleaşi valori nemodificate de apel.
Pentru a realiza o permutare a valorilor acestor variabile, este necesar să se transfere nu
valorile, ci adresele variabilelor.

Acest lucru este posibil dacă modificăm apelul în felul următor:


perm (&a, &b);
În acest caz parametrii formali ai funcţiei perm au ca valori adrese, deci ei sunt
pointeri. Având în vedere acest fapt, funcţia perm suferă următoarele modificări:
void perm(int ∗x,int ∗y); /*x si y sunt pointeri spre
intregi*/
{ int temp;
181 Limbajul de programare C
temp=∗x; /* lui temp i se atribuie continutul zonei a carei
adresa
se afla in x, adica se face
temp=a*/
*x = *y; /* in zona de la adresa x se transfera continutul
zonei a carei adresa se afla in y, deci realizeaza a = b
*/
*y = temp; /* in zona de la adresa y se transfera valoarea lui
temp,deci b = temp */
}

6.10.2. Pointeri şi tablouri


Numele unui tablou este un pointer deoarece el are ca valoare adresa primului său
element. Totuşi există o diferenţă între numele unui tablou şi o variabilă de tip pointer,
deoarece dacă unei variabile de tip pointer i se poate atribui o adresă, acest lucru nu se poate
realiza pentru numele unui tablou, el fiind întotdeauna un pointer spre primul element al
tabloului. Cu alte cuvinte, numele unui tablou trebuie considerat ca fiind un pointer constant.
Deci, dacă p este un pointer spre întregi şi a este un tablou de tip întreg, o atribuire de
forma
p = a;
este corectă. Prin această atribuire pointerul p va pointa şi el spre primul element al tabloului,
ca în figura 6.3:

a Adresa lui a(0)

p=a a(0) a(1) a(2) ... a(n)

p Adresa lui a(0)

Fig. 6.3. Atribuirea numelui unui tablou unei variabile de tip pointer.

În schimb o atribuire de forma: a = p; este interzisă, p fiind un pointer constant.


Legătura dintre tablorui şi pointeri ne oferă o altă soluţie de prelucrare a unor mulţimi
de valori, nu prin intermediul unui tablou clasic, ci prin intermediul unui pointer. Varianta cu
pointeri oferă avantajul alocării stricte a unui număr de componenete.

Să considerăm situaţia în care dorim să păstrăm mediile a n studenţi. Pentru că nu se


cunoaşte exact câţi studenţi vor fi, vom declara un tablou având un număr maxim estimat de
componente, ca în următoarea declaraţie :
Limbajul de programare C 182
float medii[30];
Această declaraţie va avea ca efect alocarea unei zone contigue de 30 de locaţii
(fiecare având dimensiunea de 4 octeţi), chiar dacă programatorul va stoca doar 25 de medii.
Varianta cu pointeri va consta în declararea unui pointer spre float, la care se va aloca exact
atâtea locaţii cât studenţi sunt.
Un alt exemplu este cel în care lucrăm cu tablouri de caractere. De exemplu, pentru a
păstra numele unei persoane, se putea folosi un tablou declarat astfel:
char Nume[20];
Varianta cu pointeri va consta în declaraţia următoare:
char *Nume;
Această variantă este mult mai flexibilă oferind posibilitatea de a aloca exact atâtea
poziţii cât este necesar.

6.10.3. Operaţii cu pointeri

Asupra pointerilor se pot face diferite operaţii, indicate în cele ce urmează.

a) Operaţii de incrementare şi decrementare

Operatorii de incrementare şi decrementare se pot aplica variabilelor de tip pointer.


Ei se execută altfel decât asupra datelor de alte tipuri.
Efect:
- operatorul de incrementare aplicat asupra unui operand de tip pointer spre tipul t
măreşte adresa conţinută de operand cu numărul de octeţi necesari pentru a păstra o dată de
tipul t;
- operatorul de decrementare se execută în mod analog.
Observaţie:
Această proprietate este foarte utilă când se au în vedere prelucrări de tablou. Fie
declaraţiile de mai jos:
int tab[n];
int *p;
int i;
unde n este o constantă întreagă, fie p = &tab[i]; unde 0<i<n-1.
În acest caz, instrucţiunea p++; măreşte valoarea lui p cu 2 şi deci va reprezenta adresa
elementului tab[i+1].

b) Adunarea şi scăderea unui întreg dintr-un pointer

Dacă p este un pointer sunt corecte expresiile de forma: p+n şi p-n unde n este de tip
întreg.
183 Limbajul de programare C
Efect:
- expresia p+n măreşte valoarea lui p cu n∗t, unde t este numărul de octeţi necesari
pentru a memora o dată de un tip spre care pointează p;
- în mod analog, expresia p-n micşorează valoarea lui p cu n∗t.
Dacă a este un tablou cu elemente de tipul t, atunci a este un pointer, deci expresia
a+n este corectă şi va reprezenta un pointer spre elementul a[n] (adresa acestuia). Valoarea
acestui element se poate obţine din expresia ∗ (a+n).
În acest fel se pot înlocui indicii de tablou prin expresii cu pointeri.

c) Compararea a doi pointeri

Doi pointeri care pointează spre elementele aceluiaşi tablou pot fi comparaţi folosind
operatorii de relaţie şi de egalitate.
Astfel, dacă p şi q sunt doi pointeri care pointează spre elementele t[i] respectiv t[j] ale
tabloului t, expresia p<q are sens şi ea este adevărată dacă i<j. De asemenea p! = q are
valoarea adevărată dacă i≠ j, etc.
Este permisă compararea pointerilor şi cu constanta simbolică NULL care se poate
interpreta ca “nici o adresă”. Astfel dacă expresia: p==NULL este adevărată înseamnă că p
nu conţine nici o adresă.

d) Diferenţa a doi pointeri

Doi pointeri care pointează spre elementele aceluiaşi tablou pot fi scăzuţi. Rezultatul
diferenţei a doi pointeri este definit astfel: fie a un tablou de un tip oarecare şi p, q doi
pointeri, p conţine adresa elementului a[i], iar q conţine adresa elementului a[i+n]. Atunci
diferenţa: q-p are valoarea egală cu n.

6.10.4. Alocarea dinamică a memoriei

Alocarea de zone de memorie şi eliberarea lor în timpul execuţiei programelor permite


gestionarea optimă a memoriei de către programe. Un astfel de mijloc de gestionare a
memoriei îl vom numi alocare dinamică a memoriei.
În acest paragraf indicăm trei funcţii din biblioteca limbajului C, utilizate frecvent în
alocarea dinamică a memoriei.
Prototipurile lor se află în fişierele standard alloc.h şi stdlib.h.

Funcţia malloc permite alocarea unui bloc de memorie a cărui dimensiune se specifică
în octeţi. Funcţia returnează un pointer spre începutul zonei alocate. Întrucât acest pointer
trebuie să permită memorarea oricărui tip de dată în zona alocată, el este de tip void*.
Prototipul funcţiei este:
Limbajul de programare C 184
void *malloc (usingned n);
unde n este numărul de octeţi ai zonei de memorie care se alocă.
În cazul în care n este prea mare, funcţia returnează pointerul NULL.
Funcţia calloc are prototipul:
void *calloc (usingned nrelem, usingned dimelem);
unde: - dimelem este dimensiunea în octeţi a unui element de dată;
- nrelem este numărul elementelor pentru are se alocă memorie.
Prin această funcţie se alocă nrelem*dimelem octeţi.
Ea returnează pointerul spre începutul zonei rezervate sau pointerul NULL în cazul în
care numărul octeţilor este prea mare (depăşeşte zona de memorie liberă afectată alocărilor
dinamice).
Elementele din zona de memorie alocată prin calloc au valoarea zero.
Funcţia free eliberează o zonă de memorie care în prealabil a fost alocată prin malloc
sau calloc. Prototipul ei este:
void free(void *p);
unde p este pointerul returnat de malloc sau calloc la alocare, deci este pointerul spre
începutul zonei care se eliberează.
Exemplu:
Funcţia memorează un şir de caracatere într-o zonă de memorie alocată prin funcţia
malloc. Ea returnează adresa de început a zonei în care s-a salvat şirul de caractere, deci
returnează un pointer spre tipul char.

#include <stdio.h>
#include <alloc.h>
#include <string.h>
char *memoreaza (char *s)
/* memoreaza sirul de caractere spre care pointeaza s in zona
de memorie furnizata de malloc*/
{
if ((p = (char *) malloc (strlen(s)+1)) ! = NULL)
{ strcpy(p,s); return p;
}
else return NULL;
}

În exemplul anterior s-au utilizat două funcţii specifice şirurilor de caractere, strlen,
care returnează lungimea unui şir de caractere şi strcpy, care copiază un şir de caractere în
altul, funcţii definite în fişierul string.h.
185 Limbajul de programare C
6.10.5. Prelucrarea tablourilor unidimensionale folosind pointeri

S-a arătat că numele unui tablou este pointer constant care are ca valoare adresa
primului element al său. Pentru declaraţia:
tip a[…];
identificatorul a are ca valoare adresa elementului a[0] , adică &a[0].
Fie tip*p; atunci atribuirea de forma p = a; este corectă şi în urma acestei atribuiri, p
are valoare tot adresa lui a[0].
Dacă utilizăm ca parametru efectiv numele unui tablou unidimensional, atunci
parametrul formal corespunzător poate fi declarat fie ca un tablou unidimensional (cu
paranteze pătrate vide), fie printr-un pointer:
tip a [100]

… f(a);

Antetul lui f se defineşte prin unul din formatele:
… f(tip t[]) sau … f(tip*t).
Indiferent care dintre antete se alege, în corpul funcţiei f se pot folosi atât variabilele cu
indici: t[exp] cât şi expresii cu pointeri: *(t+exp) pentru a face acces la elementele tabloului
tab.
S-a arătat că în general, dacă tab este un tablou unidimensional de tipul tip, atunci a+n
este chiar adresa elementului a[n]. Deci:
x = a[n] sau x = *(a+n)
sunt atribuiri care au acelaşi effect (atribuire lui x valoarea elementului a[n]). Expresiile cu
pointeri pot fi folosite şi în stânga operatorului de atribuire, adică:
a[n] = x
şi *(a+n) = x
au acelaşi efect.
Din cele de mai sus rezultă că variabilele cu indici se pot echivala prin expresii cu
pointeri. Este posibil să procedăm şi invers, adică să înlocuim expresiile cu pointeri prin
variabile cu indici, deşi acest lucru nu este recomandabil.
Fie, de exemplu, declaraţia:
tip*p = (tip*)malloc(...);
Expresia *(p+n) este echivalentă cu expresia p[n].

Exemplu:
Programul următor calculează şi afişează suma elementelor întregi ale unui vector de
dimensiune n .
#include <stdio.h>
Limbajul de programare C 186
#include <conio.h>
void main()
{ int a[100],i,n;
printf("\nIntroduceti numarul de elemente al vectorului");
scanf("%d%",&n);
printf("\nIntroduceti elementele vectorului:");
for (i=0;i<n;i++)
{ printf("\na[%d]=",i);
scanf("%d",a+i); }
int s=0;
for (i=0;i<n;i++)
s=s+*(a+i);
printf("\nSuma elementelor vectorului a este: %d",s);
getch(); }
Se observă că atât la citirea elementelor cât şi la calculul sumei s-au utilizat expresii cu
pointeri în locul expresiilor cu indici .
Legătura dintre tablouri şi pointeri ne oferă o altă soluţie de prelucrare a unor mulţimi
de valori, nu prin intermediul unui tablou clasic, ci prin intermediul unui pointer. Varianta cu
pointer oferă avantajul alocării stricte a unui număr de componenete, exact câte are nevoie
programatorul.
Vom considera un exemplu în care vom renunţa la tablouri statice şi vom folosi o
variantă dinamică, bazată pe pointeri. Fie notele la laborator ale studenţilor unei grupe, note
stocate într-un vector declarat ca pointer. Se doreşte afişarea notelor studenţilor, determinarea
mediei grupei la laborator şi numărarea a câte note peste 8 există.
Datele se pot stoca într-un vector static, de genul int Note[30], unde s-a estimat
existenţa a maxim 30 de studenţi. Varianta dinamică constă în folosirea unui pointer căruia i se
rezervă o zonă exactă de memorie, în care să se stocheze acele valori, pointer declarat astfel:
int *Note. Folosind operaţiile cu pointeri prezentate anterior, se pot accesa valorile din zona de
memorie alocată.
Prezentăm, mai jos, programul respectiv :
#include <conio.h>
#include <stdio.h>
#include <alloc.h>
int N, *Note; //se declară pointerul folosit la stocarea datelor

void Citire (void)


{ int I;
printf("\n Dati numarul de studenti");
scanf("%d",&N);
187 Limbajul de programare C
Note = (int *) malloc (N * sizeof(int)); //se aloca memorie pentru cele n
note
for (I=0; I<N ; I++)
{ printf("\n Dati nota studentului %d",I+1);
scanf("%d", Note+I); }
}
void Afisare(int *Note, int N)
{ int I;
for (I=0; I<N ; I++)
printf("\t%d",*(Note+I) );
printf("\n"); }
float Media(int *Note, int N)
{ float Med=0;
int I;
for (I=0; I<N ; I++)
Med += *(Note+I);
return Med/N; }
int Numar (int *Note, int N)
{ int I, Nr;
Nr=0;
for (I=0; I<N ; I++)
if (*(Note+I)>=8) Nr++;
return Nr; }
void main()
{ Citire();
Afisare(Note, N);
printf("\n Media notelor este %5.2f", Media(Note, N));
printf("\n Numarul notelor peste 8 este %d", Numar(Note, N));
getch();
}

6.10.6. Prelucrarea tablourilor bidimensionale folosind pointeri.Tablouri de pointeri

Numele unui tablou bidimensional, la fel ca numele unui tablou unidimensional, are ca
valoare adresa primului element al tabloului.

Fie, de exemplu, declaraţia:


tip tab[...] [...];
atunci tab are ca valoare adresa lui tab[0][0].
Limbajul de programare C 188
Dacă în cazul tablourilor unidimensionale, tab+n este adresa elementului tab[n], adică
&tab[n], în cazul tablourilor bidimensionale, expresia tab+n este adresa elementului
tab[n][0].
Această interpretare rezultă din faptul că un tablou bidimensional trebuie considerat ca
fiind un tablou de tablouri unidimensionale. În general, un tablou k dimensional este un
tablou de tablouri k-1 dimensionale.
Fie, de exemplu, declaraţia:
tip tab[m][n];
Tabloul tab poate fi privit ca m tablouri unidimensionale. Astfel, tab[0], tab[1], …,
tab[m-1] sunt cele m elemente ale lui tab şi fiecare este un pointer spre câte un tablou
unidimensional de n elemente. De aici rezultă că tab[0] are ca valoare adresa lui tab[0][0],
tab[1] are ca valoare adresa lui tab[1][0] şi în general tab[i] are ca valoare adresa lui tab[i][0].
În figura 6.4 este prezentată interpretarea tablourilor bidimensionale descrisă anterior.

tab[0] tab[0][0] tab[0][1] … tab[0][n-1]

tab[1] tab[1][0] tab[1][1] … tab[1][n-1]

... ... ... … ...

tab[m-1] tab[m-1][0] tab[m-1][1] … tab[m-1][n-1]

Fig. 6.4. Interpretarea tabloului bidimensional ca o mulţime de


tablouri unidimensionale.

Cum *(tab+i) are ca valoare chiar tab[i], rezultă că tab[i][0] are aceeaşi valoare cu
*(*(tab+i)).
De asemenea, tab[i]+j are ca valoare adresa lui tab[i][j]. Deci tab[i][j] are aceeaşi
valoare ca şi expresia *(tab[i]+j), iar aceasta din urmă are aceeaşi valoare cu expresia
*(*(tab+i)+j).
Înseamnă că atribuirile *(*(tab+i)+j) = x şi tab[i][j] = x sunt echivalente.
Dacă tab este un tablou unidimensional declarat prin:
tip tab[...];
atunci tab are tipul tip* adică pointer spre tip.
Dacă tab este un tablou bidimensional declarat prin:
tip tab[m][n];
atunci tab are tipul tip (*)[n] adică pointer spre tip.
Menţionăm că în declaraţia de mai sus m şi n sunt expresii constante.
Să presupunem că tab este parametru la apelul funcţiei f:
... f(tab).

În acest caz, parametrul formal al funcţiei f poate fi declarat în următoarele moduri:


... f (tip t[ ][n]) sau ... f(tip(*p)[n]).
189 Limbajul de programare C
Fie declaraţia:
tip *tab1[n];
În acest caz tab1 este un tablou unidimensional de pointeri spre tipul tip, adică
fiecare element din cele n ale tabloului tab1 este un pointer spre tip. Tablourile tab şi tab1 nu
trebuie confundate. Tabloului tab i se alocă o memorie de m*n*sizeof(tip) octeţi. Tabloului
tab1 i se alocă n*sizeof(tip*) octeţi.
Dacă o funcţie g are la apel ca parametru pe tabloul tab1: ... g(tab1) ... atunci
parametrul formal al funcţiei g poate fi declarat în următoarele moduri:
... g(tip*p[]) sau ...g(tip**p).
Exemple:
1. Fie declaraţiile:
double tab[2][3] = {{0, 1, 2}, {3, 4, 5}};
double *p;
În tabelul 6.7 sunt prezentate rezultate ale execuţiei unor instrucţiuni:
Tabelul 6.7.
Eleme
Valoarea
Instrucţiuni ntul
elementului
afişat
p = tab[0];
tab[0] [0]; 0
printf (“%g”, *p);
p = tab[1];
tab[1] [0]; 3
printf (“%g”, *p);

printf (“%g”, *(*(tab))); tab[0] [0]; 0

printf (“%g”, *(*(tab+1)+2)); tab[1] [2]; 5

2. Considerăm funcţia f definită astfel:


void f(double (*p)[3])
{ int i, j;
for (i=0; i<2; i++)
for (j = 0; j < 3; j ++) printf (“%g ”, p[i][j]).
}
La apelul f(tab); unde tab este tabloul definit în exerciţiul 1, se afişează valorile
elementelor lui tab separate de câte un spaţiu:
0 1 2 3 4 5
Expresia p[i][j] poate fi înlocuită cu *(*(p+i)+j).
3. Fie declaraţiile:
double *t[2];
Limbajul de programare C 190
double t0[3] = {10, 11, 12};
double t1[3] = {13, 14, 15};
şi atribuirile t[0] = t0; t[1] = t1;
Definim funcţia f1 astfel:
void f1(double *p[ ])
{

}
unde corpul funcţiei f1 coincide cu al funcţiei f.
La apelurile f1(t); se listează valorile elementelor tablourilor t0 şi t1, separate prin câte
un spaţiu, adică:
10 11 12 13 14 15
Acelaşi rezultat se obţine dacă antetul lui f1 se schimbă cu:
void f1 (double **p)
În ambele situaţii, expresia p[i][j] poate fi schimbată cu *(*(p+i)+j).
Ca o consecinţă a celor prezentate anterior, putem renunţa la tablourile bidimensionale
statice şi putem folosi tablouri de pointeri. Acest lucru este posibil pentru că pointerii, fiind
variabile, pot forma alte tipuri de date structurate, de exemplu tablouri. Dacă T este un tip de
date oarecare, tipul pointerilor spre acel tip va fi T*.
Un tablou de pointeri spre tipul T se declară prin :
T *Nume[dim];
unde Nume va fi numele tabloului de pointeri, iar dim numărul de pointeri din tablou.
Cu ajutorul unui tablou de pointeri, se pot păstra datele unei matrici, dar cu avantajul că
fiecare linie are o lungime variabilă.
Să considerăm un exemplu în care se dau notele a n candidaţi la cele m probe date la
un concurs pentru obţinerea unui post. Putem folosi un tablou de pointeri pentru afişarea
notelor fiecărui candidat şi pentru determinarea mediei obţinute de fiecare candidat. Notele
candidaţilor vor putea fi stocate şi accesate cu ajutorul unui tablou de pointeri, declarat astfel:
Note[I]
int *Note[20];
Note[0] 4 5 8 9 6 7 5
adică ca un tablou de 20 de pointeri, câte un
Note[1]
9 9 6 7 9 8 8 pointer pentru fiecare candidat. De fapt,
…. fiecare pointer va conţine adresa unei zone
Note[19] de memorie unde sunt stocate notele acelui
8 7 4 5 6 7 8 candidat (zona ce trebuie rezervată ). Acest
lucru se observă în figura 6.5:
Fig. 6.5. Memorarea notelor studenţilor
utilizând
Prezentăm pointerii.
programul sursă în varianta cu tablouri de pointeri:
#include <conio.h>
#include <stdio.h>
#include <alloc.h>
191 Limbajul de programare C
int N, M, *Note[20];
float *Media;
void Citire (void)
{ int I, J;
printf("\n Dati numarul de candidati");
scanf("%d",&N);
printf("\n Dati numarul de probe");
scanf("%d",&M);
for(I=0; I<N; I++)
Note[I] = (int *) malloc (N * sizeof(int)); //se aloca memorie pentru cele n
note
Media = (float *)malloc(N*4);
for (I=0; I<N ; I++)
{ *(Media+I)=0;
for (J=0; J<M ; J++)
{ printf("\n Dati nota studentului %d la proba %d",I+1, J+1);
scanf("%d", Note[I]+J);
*(Media+I) += *(Note[I]+J); }
*(Media+I)=*(Media+I)/M; }
}
void Afisare(int *Note[20], int N, int M)
{ int I, J;
for (I=0; I<N ; I++)
{ printf("\n Notele candidatului %d sunt", I+1);
for (J=0; J<M ; J++)
printf(" %d",*(Note[I]+J) );
printf("\tsi media %5.2f", *(Media +I));}
printf("\n"); }
void main()
{ int I;
clrscr();
Citire();
Vom prezenta, mai departe,
Afisare(Note, N, M);
un exemplu de cum se prelucrează o
for (I=0; I<N; I++) free(Note[I]); // se eliberează memoria rezervată
POPESCU ION astfel de listă de nume de persoane,
getch(); }
folosind un tablou de pointeri.
AVRAMESCU DORU
Un alt exemplu, în care tablourile de pointeri oferă Pentru exemplificare,
o soluţie mai bună din vom afişa
punct de
vedere al spaţiului ocupat, este aceea în care se lucreazănumele
cu liste deacestora
denumiri deînobiecte.
ordine

considerăm situaţia în care se păstrează numele a n persoane.alfabetică,
Fiecareconvertite
nume de în mari şi are
persoană vomo
ION ION
anumită lungime. Prin urmare, cea mai bună soluţie constăafişa cel mai
în folosirea unuilung nume.
tablou Vom
de pointeri
Fig. 6.6. Memorarea datelor personale utilizând declara lista cu numele celor n
pointerii. persoane astfel:
Limbajul de programare C 192
în care să se păstreze adresele spre zonele în care sunt stocate numele de persoane. Varianta
cu tablouri de pointeri va permite folosirea judicioasă a spaţiului de memorie, exact atât cât
este nevoie. Acest lucru se observă şi în figura 6.6.

Nume[I]
Nume[0]
Nume[1]

….
Nume[99]

char *Nume[99];
Se observă faptul că se declară 100 de adrese şi nu 100 de şiruri de caractere, soluţia
aceasta oferind o economie de memorie. Sursa programului este prezentată mai jos:
#include <conio.h>
#include <stdio.h>
#include <alloc.h>
#include <string.h>
int N;
char *Nume[99]; // lista cu adresele celor n nume, maxim 100
void Citire (void)
{ int I, J;
printf("\n Dati numarul de persoane");
scanf("%d",&N);
for(I=0; I<N; I++)
Nume[I] = (char *) malloc (N * sizeof(char));//alocarea memoriei fiecărui
nume
for (I=0; I<N ; I++)
{ printf("\n Dati numele persoanei %d ",I+1);
scanf("%s", Nume[I]);
for (J=0; J<=strlen(Nume[I]); J++)
if ((*(Nume[I]+J)>='a')&&(*(Nume[I]+J)<='z'))
*(Nume[I]+J) -= 32; //fiecare litera mica se transforma in litera mare
}}

void Ordonare(char *Nume[20], int N)


{ int I, Ok; char *Aux;
Aux = (char *) malloc(15);
do
{ Ok=0;
193 Limbajul de programare C
for (I=0; I<=N; I++)
if ( strcmp(Nume[I], Nume[I+1])>0)
{ strcpy (Aux, Nume[I]);
strcpy (Nume[I], Nume[I+1]);
strcpy (Nume[I+1], Aux);
Ok=1; }
}
while (Ok); }
void Afisare(char *Nume[20], int N)
{ int I;
Ordonare(Nume, N);
for (I=0; I<N ; I++)
printf("\n%s",Nume[I]);
printf("\n"); }
char *Cel_mai_lung(char *Nume[20], int N)
{ int I, Lmax;
char *Aux;
Aux = (char *)malloc(15);
Lmax = strlen(Nume[0]);
strcpy(Aux, Nume[0]);
for (I=0; I<N ; I++)
if (Lmax < strlen (Nume[I]))
{ Lmax = strlen(Nume[I]);
strcpy(Aux, Nume[I]); }
return Aux; }
void main()
{ int I;
clrscr();
Citire();
printf("\nLista persoanelor ordonata alfabetic");
Afisare(Nume, N );
printf("\n Cel mai lung nume este %s", Cel_mai_lung(Nume, N));
for (I=0; I<N; I++) free(Nume[I]);
getch(); }

6.10.7. Pointeri spre funcţii

Orice funcţie are asociată o adresă fixă de memorie, anume adresa de început a
funcţiei. Această adresă de început este stocată în numele funcţiei. Prin urmare, numele unei
funcţii este un pointer spre funcţia respectivă. El poate fi folosit ca parametru efectiv la
Limbajul de programare C 194
apeluri de funcţii. În felul acesta, o funcţie poate transfera funcţiei apelate un pointer spre o
funcţie. Aceasta, la rândul ei, poate apela funcţia care i-a fost transferată în acest fel.
Pointerii spre funcţii sunt pointeri speciali ce permit declarea parametrilor de tip
funcţie. Cu aceşti parametrii de tip pointer spre funcţie, putem transmite o funcţie ca
parametru la altă funcţie.
Forma generală a declaraţiei unui pointer la un tip de funcţie este :
tip (*pf) (listă parametrii formali) ;
unde tip este tipul valorii returnate de funcţie, pf va fi numele pointerului. Se remarcă prezenţa
parantezelor, fără ele declaraţia ar spune că pf este o o funcţie ce returnează un pointer. De
exemplu, declaraţia int (*p)(int x, int x) precizează că p este un pointer spre o funcţie de tip
int, care are doi parametrii.
Apelul unei funcţii prin intermediul unui pointer se face cu construcţia:
(*pf)(listă parametrii actuali);
sau var= (*pf)(listă parametrii actuali);
Să considerăm cazul în care doriţi să scrieţi o funcţie generală care să poată calcula
valoarea unei integrale dintr-o funcţie oarecare (integrabilă). De fapt, doriţi să rulaţi această
funcţie de calculul integralei cu diverse funcţii transmise de utilizator. Din păcate, în
momentul construirii funcţiei de calcul nu cunoaşteţi care este funcţia. Totuşi, acest lucru va fi
posibil folosind pointeri spre funcţii.
În primul rând, funcţiile pentru care se doreşte calculul integralei se vor putea declara
obişnuit, sub forma :
double f1(double x)
{ return x*x –1; }
sau
double f2(double x)
{ return sin (x *x); }
sau
double f3(double x)
{ return exp(x); }
Funcţia generală de calcul ( bazată pe o anumită metodă de integrare) va trebui să aibă
prototipul următor :
double Integrala( double A, double B, int N, double (*p)(double) );
unde A , B reprezintă capetele de integrare iar n numărul de puncte pentru diviziunea aleasă
(vom folosi o metodă de integrare bazată pe analiză numerică, folosind metoda trapezelor).

Pointerul spre funcţie este declarat astfel :


double (*p)(double) .
Să încercăm să interpretăm această construcţie :
• *p - este un pointer
195 Limbajul de programare C
• (*p)(double) - înseamnă că p este un pointer spre funcţii cu argumente de
tip double.
• double (*p)(double) - înseamnă că p este un pointer spre funcţii care
returnează o valoare de tip double şi are ca parametru un double.
Pointerul p va putea fi folosit pentru a accesa valorile funcţiei. Apelarea funcţiei se
face cu expresia (*p)(x). Mai mult, funcţia generală construită anterior, se va putea apela
( pentru exemplele de funcţii prezentate anterior) astfel :
ValInt= Integrala(10, 20, 100, f1);
sau
A=-3.14; B=3.14 ; // adică A şi B sunt egale cu –Pi şi Pi
ValInt= Integrala(A, B, 100, f2);
Trebuie să atragem atenţia asupra faptului că construcţia double *p(double) este
corectă, dar are altă semnificaţie: reprezintă prototipul unei funcţii ce returnează un pointer
spre double (!!). Acest lucru este datorat faptului că parantezele () sunt mai prioritare decât
operatorul *.
Prezentăm programul care calculează valoarea integralei.
#include <graphics.h>
#include <math.h>
#include <stdio.h>
#include <conio.h>
double f(double x)
{ return x*x-1; }
double f1(double x)
{ return sin(x*x); }
double f2(double x)
{ return exp(x); }
double Integrala(double A, double B, int n, double (*p)(double))
{ double pas,S;int i;
pas=(B-A)/n;
S=0;
for(i=0;i<n;i++)
S=S + (*p)(A+i*pas);
S=S + (*p)(A)/2 + (*p)(B)/2;
S=S*pas;
return S; }

void main()
{ double ValInt,A,B;
ValInt=Integrala(1,2,100,f);
printf("\n valoarea integralei din f=x*x-1 este %lf",ValInt);
Limbajul de programare C 196
A=-3.14;
B=3.14;
ValInt=Integrala(A,B,100,f1);
printf("\n valoarea integralei din f=sin(x*x) este %lf",ValInt);
ValInt=Integrala(10,100,100,f2);
printf("\n valoarea integralei din f=exp(x) este %lf",ValInt);
getch(); }
197 Limbajul de programare C
6.10.8. Tratarea parametrilor din linia de comandă

În linia de comandă folosită la apelul execuţiei unui program se pot utiliza diferiţi
parametri. În cazul utilizării mediului de dezvoltare integrat Borland C, aceşti parametri se pot
defini utilizând submeniul Arguments al meniului Options. Se selectează meniul Options
folosind săgeţile sau tastând <ALT>O. Apoi se selectează submeniul Arguments cu ajutorul
săgeţilor sau tastând A. În acest moment se afişează o fereastră şi se vor tasta parametrii care
să fie prezenţi la lansarea programului. Parametrii se tastează unul după altul, separaţi prin
blancuri. După ultimul parametru se va acţiona tasta ENTER.
Aceşti parametri pot fi utilizaţi parametrii argc şi argv ai funcţiei principale.
Parametrul argc este de tip întreg şi indică numărul de parametri din linia de comandă
(sau diferiţi cu ajutorul submeniului Arguments). Parametrul argv este un tablou de pointeri
spre zonele în care sunt păstraţi parametrii liniei de comandă. Aceştia se consideră şiruri de
caractere. În felul acesta, antetul funcţiei principale va fi:
main (int argc, char *argv[])
Exemplu:
Considerând că la lansarea programului prog s-au furnizat parametrii:
15 SEPTEMBRIE 2001
În acest caz argc = 4, iar tabloul argv conţine pointerii:
- argv[0] - pointeri spre numele programului (calea, numele şi extensia .EXE);
- argv[1] - pointeri spre „15”;
- argv[2] - pointeri spre „SEPTEMBRIE”;
- argv[3] - pointeri spre „2001”.
Observaţii:
1) Lansarea unui program se face cu prima instrucţiune a funcţiei principale. Parametrii
argc şi argv au deja în acest moment valorile indicate mai sus. Deci ei pot fi analizaţi chiar
începând cu prima instrucţiune a funcţiei principale.

2) În mod frecvent aceşti parametri reprezintă diferite opţiuni ale programului, date
calendaristice, nume de fişiere etc.
3) argv[0] este întotdeauna pointerul spre numele fişierului cu imaginea executabilă a
programului.
Exemple:
1. Programul următor afişează parametrii din linia de comandă.

void main(int argc, char *argv[])


/*afiseaza parametrii din linia de comanda */
{ int i;
for (i = 0; i<argc; i++)
printf(“%s\n, argv[i]”);
Limbajul de programare C 198
}
2. Programul următor preia din linia de comandă o dată calendaristică compusă din trei
parametri: zi, denumire_luna, an, testează dacă este validă şi în caz afirmativ o rescrie sub
forma:
zz/ll/aaaa
unde:
- zz este ziua pe doua cifre;
- ll este numărul lunii pe două cifre;
- aaaa este anul pe patru cifre.
Aşa cum s-a indicat mai sus, parametrii liniei de comandă sunt păstraţi sub formă de
şiruri de caractere. De exemplu, data calendaristică 15 septembrie 2001 se păstrează prin trei
şiruri de caractere:
„15” „septembrie” „2001”
Ziua şi anul vor trebui convertite din şiruri de caractere în întregi binari. În acest scop,
se poate folosi funcţia de bibliotecă atoi, care are ca parametru pointerul spre şirul de caractere
ce reprezintă numărul de convertit, iar la revenire returnează întregul binar rezultat din
conversie. Prototipul acestei funcţii se află în fişierul stdlib.h.
Denumirea lunii se caută cu ajutorul funcţiei strcmp, comparându-se cu elementele
tabloului de pointeri tpdl, iniţializat cu denumirile lunilor calendaristice. Funcţia strcmp
are prototipul în fişierul string.h. În acest exemplu, argc trebuie să aibă valoarea 4, iar
argv are elementele definite în felul următor:
- argv[0] - pointeri spre numele programului (calea, numele şi extensia
.EXE);
- argv[1] - pointeri spre zi;
- argv[2] - pointeri spre denumirea lunii;
- argv[3] - pointeri spre an.
Programul care realizează cele escrise anterior este următorul:

#include <stdlib.h>
#include <string.h>
void main(int argc, char*argv [])
{ static int tabzi [] = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
static char *tpdl[] = {
“luna ilegala”, “ianuarie”, “februarie”, “martie”, “aprilie”, “mai”, “iunie”,
“iulie”, “august”, “septembrie”, “octombrie”, “noiembrie”, “decembrie” };
int zz, ll, aa, i;
if (argc! = 4) printf (“numar parametric eronat\n”);
else
if(strlen(argv[1])>2) printf(“peste 2 cifre la zi\n”) ;
199 Limbajul de programare C
else
if(strlen(argv[10])>10)
printf(„peste 10 caractere la denumire luna\n”);
else
if(strlen(argv[3])>4) printf(„peste 4 cifre la an\n”);
else { /*1 */
zz = atoi (argv[1]); /* se cauta luna in tpd1 */
for (ll = 1; ll<13; ll++)
if(strcmp (argv[2], tpd1[11])==0) break;
if(ll= = 13) /* nu s/a gasit luna */
printf(„denumirea lunii eronata\n”);
else { /* 2 */ /* se converteste anul */
aa = atoi (argv[3]);
if(aa<1600) printf(„anul eronat\n”);
else { /* 3 */
i = tabzi [11] + (ll = = 2&&(aa%4 = = 0&& aa%100!=0 aa%400= =0));
if (zz<1  zz>i) printf(„ziua eronata\n”);
else /* data calendaristica corecta */
printf(„%02d/%02d/%d\n”, zz, ll, aa);
} /* sfarsit else 3*/
} /* sfarsit else 2*/
} /* sfarsit else 1*/
} /* sfarsit main*/
În acest program s-a utilizat funcţia atoi care realizează o conversie a unui şir de
caractere într-un întreg. Funcţia se găseşte în fişierul antet stdlib.h.

6.10.9. Modificatorul const

Am văzut că o constantă se defineşte prin caracterele care intră în compunerea ei. De


asemenea s-a arătat că putem atribui un nume unei constante printr-o construcţie #define.

Un astfel de nume se spune că este o constantă simbolică şi el se substituie prin şirul


de caractere care îi corespunde în zona de preprocesare.
Un alt mod de a defini o constantă este acela de a folosi modificatorul const într-o
declaraţie. Printr-o astfel de declaraţie, unui nume i se poate atribui o valoare constantă. În
acest caz, numele respectiv nu mai este tratat de preprocesor şi el poate fi folosit în program în
mod analog cu numele variabilelor.
Observaţie:
Unui nume declarat cu ajutorul modificatorului const nu i se poate schimba valoarea
printr-o expresiei de atribuire, ca şi unei variabile obişnuite.
Limbajul de programare C 200
Formatele declaraţiei cu modificatorul const sunt următoarele:
tip const nume = valoare;
const tip nume = valoare;
tip const nume;
const tip nume;
const nume= valoare;
const nume;
Exemple:
1. const i = 10
în urma căreia i devine egal cu 10. O expresie de atribuire de forma i = 10 este eronată.
2. const double pi = 3.14159265;
la care ulterior nu este posibil să schimbăm valoarea lui pi, scriind de exemplu pi = 3.14159;
3. char*const s=”sir”;
în care s este un pointer constant spre zona în care este păstrat şirul de caractere. Valoarea lui
s nu poate fi schimbată. Cu toate acestea, conţinutul zonei spre care pointează s poate fi
schimbat.
Atribuirea s = t; unde t este un pointer spre caractere, nu este acceptată de compilator.
În schimb, *s =’1’; sau *(s+1) = ’a’; sunt instrucţiuni corecte şi vor modifica primul, respectiv
al doilea caracter al zonei spre care pointează s.
4. char const *s=”sir”;
în care s este un pointer spre o zonă constantă. În acest caz valoarea lui s poate fi schimbată,
de exemplu s = t;. În schimb, o instrucţiune de forma s =’1’; este o eroare, deoarece prin
declaraţia de mai sus s-a dat compilatorului indicaţia că zona spre care pointează s este
constantă. Zona respectivă poate fi modificată numai prin intermediul unui alt pointer. De
exemplu, fie char*p; Atunci p = s; şi *p=’1’; sunt corecte.
5. const char *s=”sir”;
este identică cu declaraţia de la exemplul 4.
Programul ilustrează câteva cazuri de utilizare a modificatorului const în declaraţii,
precum şi modurile indirecte de modificare a constantelor declarate în acest fel.

#include <string.h>
void main()
{
int const i=10; int const j;
double const x = 1.23456789; double const pi;
const n=12; int const k; const 1;
char *t=”xyz”; char *const s=”sir”; char const *s1 = “abc”;
const s2 = “ABCDE”; char *p; int *q;
q = &j; *q = 1234;
201 Limbajul de programare C
*(int *) &k=3;
*(int *) &1 = -3;
printf(“dimensiunile lui I,j,n,1\n”);
printf(“%d %d %d %d\n”, sizeof(i), sizeof(j), sizeof(n), sizeof(1));
printf(“dimensiunile lui x,s,s1,s2\n”);
printf(“%d %d %d %d\n”, sizeof(x), strlen(s), strlen(s1), strlen(s1),
strlen(s2));
printf(“valorile lui i,n,s,s1,s2\n”);
printf(“i = %d n=%d s=%s s1=%s s2=s%\n”, i,n,s,s1,s2);
printf(“valorile lui j,k,1\n”);
printf(“j=%d k=%d l=%d\n”, j,k,l);
*(double *) &pi=3.14159;
printf(“valorile lui x,pi\n”);
printf(“x=%.10f pi=%.10f\n”, x,pi);
/* modificari folosind pointeri */
*(int *)&i = 10000;
q=&l; *q=7;
printf(“valorile modificate ale lui i si l\n”);
printf(“i=%d l=%d\n”, i, l);
*(double *) &pi = 3.14159265;
printf(“valorea modificata a lui pi\n”);
printf(“%.10f\n”, pi);
*s = ‘1’; *(s+1) = ‘2’;
printf(“valorea modificata a zonei spre care pointeaza s\n”);
printf(“s=%s\n”, s);
p = s1; *p=’1’;
printf(“valorea modificata a zonei spre care pointeaza s1\n”);
printf(“s=%s\n”, s1);
}

Rezultatele programului:
dimensiunile lui i,j,n,1
2 2 2 2
dimensiunile lui x,s,s1,s2
8 3 3 5
valorile lui I,n,s,s1,s2
i = 10 n = 12 s = sir s1 = abc s2 = ABCDE
Valorile lui j,k,l
j = 1234 k=3 l=3
Limbajul de programare C 202
Valorile lui x, pi
x = 1.2345678900 pi=3.1415900000
Valorile modificate ale lui I si l
i = 10000 l=7
valoarea modificata a lui pi
3.1415926000
valoarea modificata a zonei spre care pointeaza s
s = 12r
valoarea modificata a zonei spre care pointeaza s1
s1 = 1bc
Observaţii:
1) Declaraţia const permite protejarea valorii atribuite unui nume faţă de eventualele
încercări ulterioare de a modifica accidental aceste valori prin simple atribuiri.
2) În cazul în care programatorul doreşte să facă o astfel de modificare, ea se poate
realiza, dar nu direct, ci doar indirect, folosind pointerii.
De exemplu, dacă dorim să modificăm valoarea constantei i din exemplu 1 de la 10 la
100, putem realiza acest lucru prin instrucţiunea următoare:
*(int*)&i = 100
Aceasta este posibil deoarece unui nume declarat prin const i se alocă memorie.
Expresia de mai sus este echivalentă cu secvenţa:
int *p;
................
p = &i;
*p = 100;
Într-adevăr, &i reprezintă adresa zonei alocate numelui i. Atunci (int *)&i converteşte
această adresă spre un pointer spre întregi, deci ea are aceeaşi valoare ca şi p după
instrucţiunea p = &i; Prin urmare, instrucţiunea *p = 100; devine echivalentă cu
*(int*)&i = 100;
Modificatorul const se foloseşte frecvent la declaraţia parametrilor formali de tip
pointer. O astfel de declaraţie are formatul:

Un parametru formal declarat prin construcţia:


tip*nume_parametru_formal
corespunde unui parametru efectiv a cărui valoarea este o adresă.
La apel, valoarea parametrului formal devine egală cu această adresă. Datorită acestui
fapt funcţia apelată poate să modifice data aflată la adresa respectivă.
Modificatorul const utilizat la declararea unui astfel de parametru formal interzice
funcţiei apelate să modifice data de la adresa recepţionată la apel de către parametrul formal
corespunzător.
203 Limbajul de programare C
Acest mecanism este utilizat frecvent în cazul funcţiilor de tratare a şirurilor de
caractere.
Exemple:
1. Funcţia strlen din biblioteca standard a limbajului C are prototipul:
unsigned strlen(const char*s);
Ea se apelează prin expresii de atribuire de forma:
n = strlen(x);
unde x este un pointer spre o zonă de memorie în care se află un şir de caractere.
Funcţia strlen determină lungimea şirului aflat la adresa recepţionată de către
parametrul s. Ea nu are voie să modifice şirul respectiv şi din această cauză parametrul s se
declară folosind modificatorul const.

2. Funcţia strcpy din biblioteca standard a limbajului C are prototipul:


Char * strcpy (char*dest, const char * sursa);
Această funcţie copiază şirul aflat la adresa recepţionată de parametrul sursă în zona
de memorie a cărei adresă este atribuită parametrului dest. Deoarece funcţia strcpy nu are voie
să modifice şirul de la adresa sursă, la destinaţia acestui parametru s-a utilizat modificatorul
const. În schimb, funcţia schimbă conţinutul de la adresa dest, unde se copiază şirul şi deci
parametrul dest nu se mai declară prin modificatorul const.

Funcţia strcpy returnează adresa din dest, adică adresa zonei în care s-a copiat şirul.
3. Funcţia strcat din biblioteca standard a limbajului C are prototipul:
char*strcat(char*a, const char*b);
Şirul spre care pointează b se concatenează la sfârşitul şirului spre care pointează a.
Şirul spre care pointează nu poate fi modificat de către funcţia strcat şi din această cauză
parametrul b este declarat cu modificatorul const.
Funcţia returnează pointerul spre şirul rezultat, deci chiar valoarea parametrului a.

4. Funcţia strcmp din biblioteca standard a limbajului C are prototipul:


int strcmp (const char *a, const char *b);
Funcţia compară şirurile spre care pointează a şi b. Ea nu poate modifica cele două
şiruri şi de aceea cei doi parametri au fost declaraţi prin modificatorul const.

6.10.10. Stiva, exemplu de structură ce foloseşte pointeri


Prin stivă se înţelege o mulţime ordonată de elemente la care accesul se realizează
conform principiului LIFO (Last In First Out).
Cel mai simplu procedeu de implementare a unei stive este păstrarea elementelor ei
într-un tablou unidimensional.
În zona de memorie afectată stivei se pot păstra elementele ei unul după altul. De
asemenea ele se pot scoate din această zonă în ordine inversă păstrării lor. Astfel, la un
moment dat, se scoate ultimul element pus în stivă şi numai acesta.
Limbajul de programare C 204
Despre ultimul element pus pe stivă se spune că este în vârful stivei, despre primul că
este la baza stivei.
Vârful stivei se modifică de fiecare dată când punem un element pe stivă în aşa fel
încât lungimea părţii ocupate a stivei să crească.
Elementul care se scoate din stivă este elementul aflat în vârful stivei. Prin aceasta,
partea ocupată din stivă se micşorează. Cu alte cuvinte accesul este permis doar la vârful
stivei:
- un element se poate pune pe stivă numai după elementul aflat în vârful stivei şi după
această operaţie el ajunge în vârful stivei;
- se poate scoate de pe stivă numai elementul aflat în vârful stivei şi după această
operaţie în vârful stivei rămâne elementul care a fost pus pe stivă înaintea lui.
În felul acesta, se observă că elementele unei stive sunt gestionate după principiul
numit FIFO: ultimul element pus pe stivă este primul element care se scoate din stivă.
O astfel de gestiune se obţine simplu dacă reorganizăm zona de memorie afectată stivei
ca un tablou unidimensional şi în plus se definesc două funcţii: una care pune un element pe
stivă şi alta care scoate un element din stivă.
Denumirile utilizate mai jos pentru aceste funcţii sunt denumiri deja consacrate în
literatura de specialitate. Astfel, vom numi:
- push funcţia care pune un element pe stivă;
- pop funcţia care scoate un element din stivă.
De asemenea, vom denumi stack tabloul utilizat în implementarea stivei şi next
variabila care indică prima poziţie liberă din stivă.
Deci stack va fi tabloul unidimensional, pe care îl vom aloca static în momentul de faţă
şi îl vom considera de tip întreg. Aceasta înseamnă că elementele păstrate în stivă sunt numere
întregi (de tip int). Evident, se pot considera stive care să păstreze şi date de alte tipuri.
Rezultă că stack[0] este elementul de la baza stivei. Dacă ultimul element pus pe stivă
este stack[n], atunci acesta se află în vârful stivei. Variabila next defineşte prima poziţie liberă
din stivă, deci ea indică elementul stack[n+1].
Principalele funcţii de lucru cu stiva implementate în limbajul C sunt:

- clear funcţia de iniţializare a stivei. După apelul ei stiva devine vidă;


- top permite acces la elementul din vârful stivei, la fel ca pop, fără a
elimina însă acest element;
- empty returnează o valoare diferită de zero (adevărată) dacă stiva
este
vidă şi zero în caz contrar;
- full returnează o valoare diferită de zero dacă stiva este plină şi zero
în caz contrar.
205 Limbajul de programare C
Exemplul 1: Se vor defini funcţiile push, pop şi clear pentru o stivă de tip înreg de
maxim 1000 de elemente.
# define MAX 1000
static int stack[MAX]
static next = 0; /* indicele pentru baza
stivei */
void push(x) /* pune pe stiva valoarea
lui x */
int x;
{ if (next<MAX) stack [next++] = x;
else printf(“stiva este depasita\n”); }
int pop() /*scoate elementul din varful stivei si returneaza valoarea
lui */
{ if (next>0) return stack [--next];
else { printf (“stiva este vida\n”); return 0; }
}
void clear() /* videaza
stiva */
{ next = 0;
}
Exemplul 2: Să se rescrise funcţiile push, pop şi clear folosind pointeri în locul
variabilelor cu indici, considerând un număr maxim de 1000 de elemente.
# define MAX 1000
static int stack[MAX]
static int *next = stack;
/* pointerul next se initializeaza cu adresa de indice a zonei afectate stivei */
void push(int x) /* pune pe stiva valoarea
lui x */
{ if (next<stack+MAX) *next++ = x;
else printf(“stiva este depasita\n”); }
int pop() /*scoate elementul din varful stivei si returneaza valoarea
lui */
{ if (next>stack) return (*--next);
else { printf (“stiva este vida\n”); return 0;}
}
void clear() /* videaza
stiva */
{ next = stack; }
Limbajul de programare C 206
6.11. Structuri şi tipuri definite de utilizator

În anumite situaţii practice se lucrează cu seturi mari de date. Aceste date pot să fie
toate de acelaşi tip sau să fie de tipuri diferite. Dacă datele sunt de acelaşi tip ele se pot grupa
în structuri de tip tablou. Dacă sunt de tip diferit, spunem despre grupa respectivă de date că
formează o structură.

6.11.1. Declaraţia de structură

O structură se poate declara în felul următor:


struct nume { listă de_declaratii } nume1, nume2, …, numen;
unde nume1, nume2, …, numen sunt nume care pot şi lipsi, dar nu toate deodată.
Exemple:
1. Structurile de tip dată calendaristică se pot declara în felul următor:
struct data_calend { int zi;
char luna [11];
int an; }
data_nasterii, data_angajarii;
Prin această declaraţie s-a introdus tipul struturat data_calend, iar data_nasterii şi
data_angajarii sunt structuri de tipul data_calend.
O declaraţie de forma:
struct nume { lista_de_declaratii};
introduce un tip nou de date (un tip structurat). Acest tip are numele nume şi poate fi folosit
în continuare pentru a declara date, singura diferenţă faţă de tipurile predefinite fiind aceea că
este precedat de cuvântul rezervat struct.
Prin declaraţia
struct nume1, nume2, …, numen;
se declară nume1, nume2, …, numen ca structuri de tipul nume.
Observaţii:
1. Printr-o declaraţie de forma:
struct nume { lista_de_declaratii
};
lui nume nu i se alocă memorie. O astfel de declaraţie o vom numi în continuare declaraţia de
tip.
2. O declaraţie de forma:
struct nume nume1, nume2, …, numen;
este tratată de compilator ca şi declaraţiile obişnuite. Compilatorul alocă memorie structurilor
nume1, nume2, …, numen conform tipului nume.
Considerăm un model simplificat ce păstrează datele personale:
207 Limbajul de programare C
- nume_prenume;
- adresa;
- data_nasterii;
- data_angajarii.
Prin declaraţia de mai jos, introducem tipul data_pers:
struct data_pers { char nume_prenume [50];
char adresa [50];
struct data_calend
data_nasterii, data_angajarii;
};

6.11.2. Accesul la elementele unei structuri

Accesul la elementele unei structuri se realizează printr-o construcţie de forma:


nume.nume_câmp
unde: - nume este numele datei structurate;
- nume_câmp este numele componentei pe care vrem să o referim.
Punctul, utilizat în construcţia de mai sus este un operator de prioritate maximă. El se
află în tabela de priorităţi în aceeaşi linie cu parantezele.
Fie declaraţiile:
struct data_calend tdc[100], data;
În acest caz, tdc este un tablou ale cărui element sunt structuri de tipul data_calend. Ne
putem referi la ziua corespunzătoare elementului de indice i prin construcţia tdc[i].zi. În
schimb, pentru variabila data referirea se face cu construcţia data.zi.

6.11.3. Atribuirea unor nume pentru tipuri de date

Tipurile de bază ale limbajului C, numite şi tipuri predefinite, se identifică printr-un


cuvânt cheie (int, char, float etc.). Tipurile structurate se definesc printr-o declaraţie de forma:
struct nume {…};
Programatorul poate să atribuie un nume unui tip, indiferent de faptul că acesta este un
tip predefinit sau definit de utilizator. Aceasta se realizează prin intermediul construcţiei
typedef. Ea are următorul format:
typedef tip nume_tip;
unde:
- tip este fie numele unui tip predefinit, fie o declaraţie de tip structurat;
- nume_tip este numele atribuit tipului respectiv.
După ce s-a atribuit un nume unui tip, numele respectiv poate fi utilizat pentru a
declara date de acel tip, exact la fel cum se utilizează în declaraţii cuvintele cheie int, char,
float etc.
Limbajul de programare C 208

Observaţie:
De obicei, numele asignat unui tip se scrie cu litere mari. Un exemplu de astfel de
nume există în fişierul stdio.h, pentru tipul fişier, căruia i s-a atribuit numele FILE.
Exemple:
1. Fie declaraţiile:
typedef int INTREG;
typedef float REAL;
În continuare, denumirile INTREG şi REAL se pot folosi la fel ca şi cuvintele cheie
int şi float. Cu alte cuvinte, declaraţia:
INTREG x,y,a [100];
este identică cu declaraţia: int x,y,a [100];
În mod analog:
REAL p,q,r;
este identică cu declaraţia: float p,q,r;
2. typedef struct dat_calend { int zi;
char luna [11];
int an;
} DC;
Prin această declaraţie se atribuie denumirea DC tipului structurat data_calend. În
continuare putem declara date de tip DC:
DC data_angajarii, data_nasterii;
DC data_crt = {20, “septembrie”, 1991};
3. Programul următor citeşte numere complexe aflate în fişierul standard stdin şi le
rescrie împreună cu modulul lor. Un număr complex se introduce printr-o pereche de
numere flotante, primul reprezentând partea reală, iar cel de al doilea partea imaginară.
typedef struct { double real;
double imaginar;
} COMPLEX;
double modul (COMPLEX *);
void main ()
/*citeste numere complexe si le rescrie impreuna cu modulul lor*/
{ COMPLEX z;
while (scanf(“%lf %lf”, &z.real, &z.imaginar)= = 2)
printf (“%lf %lf, modul = %lf\n”,z.real, z.imaginar, modul(&z)); }
#include <math.h>
double modul (COMPLEX *x)
/*returneaza modulul numarului complex spre care pointeaza x*/
{ return sqrt (x->real* x->real+x->imaginar* x->imaginar); }
209 Limbajul de programare C

Pentru a exemplifica cele prezentate anterior, vom prezenta un program ce permite


operarea cu datele personale ale unui student: număr matricol, nume, data naşterii, media sa
generală din anul anterior, adresa sa. Vom dori să scriem un program care determină ce vârstă
are studentul şi care afişează datele personale. Pentru aceasta vom defini un tip nou de date
numit Student, care să păstreze datele unui singur student. Data naşterii, va fi declarată şi ea
ca o altă structură existentă în structura iniţială, lucru care este permis. Pentru a determina
vârsta, vom afla data sistemului (folosind struct date, o structură a sistemului de tip dată
calendaristică şi funcţia getdate ). Programul este prezentat în continuare:
#include <conio.h>
#include <stdio.h>
#include <string.h>
#include <dos.h>
typedef struct stud{
unsigned nr_mat;
char nume[20], adr[30];
struct { unsigned zi,luna,an; } dn;
float med_gen;
} Student;
void main()
{ int Varsta;
Student S;
struct date d;
clrscr();
printf("\n Dati numarul matricol ");
scanf("%u", &S.nr_mat);
printf("\n Dati numele ");
scanf("%s", S.nume);
printf("\n Dati adresa ");
scanf("%s", S.adr);
printf("\n Dati data nasterii in forma zz:ll:aaaa: ");
scanf("%2d:%2d:%4d",&S.dn.zi,&S.dn.luna,&S.dn.an);
printf("\n Dati media generala ");
scanf("%f", &S.med_gen);
clrscr();
printf("Studentul %s este nascut in %2d:%2d:%4d, locuieste in %s si are
media %5.2f", S.nume,S.dn.zi,S.dn.luna,S.dn.an,S.adr,S.med_gen);
getdate(&d); //se afla data sistemului
Varsta=d.da_year-S.dn.an;
Limbajul de programare C 210

if (d.da_mon <S.dn.luna) //daca nu s-a implinit o luna


Varsta--;
if ((d.da_mon ==S.dn.luna) && (d.da_day<S.dn.zi)) //daca nu s-a implinit
ziua
Varsta--;
printf("\n Astazi suntem in %2d:%2d:%4d iar varsta este %d\n", d.da_day,
d.da_mon,d.da_year,Varsta);
getch();
}

Un alt exemplu îl constituie programul următor care permite citirea de la tastatură a


datelor despre cei n candidaţi la examenul de admitere la facultate. Se presupune că admiterea
în facultate se face doar după media obţinută de candidat la bacalaureat şi că la facultatea
respectivă există trei profile la care candidatul se poate înscrie. Un candidat are dreptul să
specifice o ordine de preferinţă a celor trei profile (notate în program cu a, b şi c).
Cunoscându-se numărul de candidaţi, n, numărul de locuri disponibil la profilele a – na, b –
nb şi la profilul c – nc , precum şi datele despre cei n candidaţi (nume, prenume şi media la
bacalaureat) se cere programul C care să efectueze repartiţia candidaţilor pe opţiuni şi
afişarea candidaţilor admişi şi a celor respinşi.
#include<stdio.h>
#include<conio.h>
typedef struct
{ char nume[20],pren[20];
float med;
char opt[3];
char repartizat;
} Candidat;
Candidat a[100];
int n;
int na,nb,nc;

void citire_date()
{ printf("\nNumar candidati:");
scanf("%d",&n);
printf("\nNumar locuri la profilul a:");
scanf("%d",&na);
printf("\nNumar locuri la profilul b");
scanf("%d",&nb);
printf("\nNumar locuri la profilul c");
211 Limbajul de programare C
scanf("%d",&nc);

float x;
int i,j;
for (i=0;i<n;i++)
{ printf("\nNumele:");
scanf("%s",a[i].nume);
printf("\nPrenumele:");
scanf("%s",a[i].pren);
printf("\nMedia :");
scanf("%f",&x);
a[i].med=x;
for (j=0;j<3;j++)
{ printf("\nOptiunea %d ",j+1);
fflush(stdin);
scanf("%c",&a[i].opt[j]); }
a[i].repartizat=' ';
}
}

void ordonare_medii()
{ int i,gata;
Candidat temp;
do
{ gata=1;
for (i=0;i<n-1;i++)
if (a[i].med<a[i+1].med)
{ temp=a[i];
a[i]=a[i+1];
a[i+1]=temp;
gata=0; }
}
while (gata==0);
}

void afisare_candidati()
{ int i;
printf("\nLista candidatilor este\n");
printf("\n*************************************");
for (i=0;i<n;i++)
Limbajul de programare C 212
printf("\n%20s %20s %.2f",a[i].nume,a[i].pren,a[i].med);
}
void repartizare_optiuni()
{ int i,j;
for (i=0;i<n;i++)
for (j=0;j<3;j++)
if (a[i].repartizat==' ')
switch (a[i].opt[j])
{ case 'a':if (na>0)
{ a[i].repartizat='a';
na--; }
break;
case 'b':if (nb>0)
{ a[i].repartizat='b';
nb--; }
break;
case 'c':if (nc>0)
{ a[i].repartizat='c';
nc--; }
break
}
}

void afisare_rezultate()
{ int i,j;
printf("\nLista cu candidatii admisi este ");
printf("\n**********************************");
for (i=0;i<n;i++)
if (a[i].repartizat!=' ')
printf("\n%20s %20s media %.2f profilul %c",a[i].nume,a[i].pren,a[i].med,
a[i].repartizat);
printf("\nLista cu candidatii respinsi este ");
printf("\n**********************************");
for (i=0;i<n;i++)
if (a[i].repartizat==' ')
printf("\n%20s %20s media %.2f ",a[i].nume,a[i].pren,a[i].med);
}

void main()
213 Limbajul de programare C
{ citire_date();
afisare_candidati();

ordonare_medii();
repartizare_optiuni();
afisare_rezultate();
getch();
}
6.11.4. Uniuni

Un caz special de structuri îl constituie uniunile. Acestea sunt structuri alternative


pentru care dimensiunea spaţiului necesar memorării lor este egală cu cea mai mare
dimensiune necesară memorării unei componente a acelei structuri. Astfel, toate componentele
uniunii ocupă aceeaşi zonă în cadrul memoriei. Tocmai de aceea, la un moment dat, spre
deosebire de structuri, este accesibilă o singură componentă a unei uniuni. Uniunile sunt utile
pentru situaţii în care se folosesc simultan variabilele, obţinând o economie de memorie.
Uniunile se definesc în aceeasi manieră ca şi structurile, cuvântul cheie utilizat fiind union.
union un_tip {
int uint;
float ufloat;
char uchar;
} o_variabila;
Dacă, la un moment dat, variabila o_variabila are încărcat elemental uint, presupunem
cu valoarea 15897, atunci accesul către celelalte două componente este dacă nu ilegal, cel
puţin lipsit de sens. Aceasta deoarece în spaţiul rezervat în memorie variabilei respective se
află un număr întreg şi deci nu are sens să căutăm un caracter sau un număr real.
Exemplu:
Programul următor calculează ariile pentru următoarele figuri geometrice: cerc, pătrat,
dreptunghi şi triunghi. Datele programului, introduse de la tastatură vor fi de forma:
C , P, D, T – un caracter care indică tipul figurii, Raza – pentru cerc, latura pentru
patrat, cele doua laturi pentru dreptunghi, respectiv cele trei laturi pentru triunghi.
În cazul triunghiului se utilizează formula lui Heron pentru calculul ariei:
aria = p ⋅ ( p − a ) ⋅ ( p − b) ⋅ ( p − c )
unde:
a,b,c sunt laturile triunghiului;
a+b+c
p= , este semiperimetrul triunghiului,
2
condiţia de existenţă a triunghiului fiind: p>a, p>b şi p>c.
Programul este prezentat în continuare:
Limbajul de programare C 214
#include<stdio.h>
#include<math.h>
#include<conio.h>

#define Pi 3.1415927
#define Cerc 1
#define Patrat 2
#define Dreptunghi 3
#define Triunghi 4
typedef struct { int tip_figura;
union{ float raza;
float latura;
float lat_drept[2];
float lat_tr[3]; } figura;
} FIGURA;
void main()
{ float aria,p;
char tip;
int i;
FIGURA fig; //se citeste caracterul care defineste tipul
figurii
printf("\nIntroduceti tipul figurii:");
scanf("%c",&tip);
switch (tip) { case 'C': //cerc
printf("\nIntroduceti raza cercului:");
scanf("%f",&fig.figura.raza);
fig.tip_figura=Cerc;
break;
case 'P': //patrat
printf("\nIntroduceti latura patratului");
scanf("%f",&fig.figura.latura);
fig.tip_figura=Patrat;
break;
case 'D': //dreptunghi
printf("\nIntroduceti laturile dreptunghiului");
scanf("%f%f",&fig.figura.lat_drept[0],
&fig.figura.lat_drept[1]);
fig.tip_figura=Dreptunghi;
break;
215 Limbajul de programare C
case 'T':
//triunghi
printf("\nIntroduceti laturile triunghiului");
for (i=0;i<3;i++)
scanf("%f",&fig.figura.lat_tr[i]);

fig.tip_figura=Triunghi;
break;
default: printf("\nEroare");
break;
}
switch (fig.tip_figura)
{ case Cerc: printf("\nAria cercului este %f",fig.figura.raza*fig.figura.raza*Pi );
break;
case Patrat:printf("\nAria patratului este %f"
,fig.figura.latura*fig.figura.latura);
break;
case Dreptunghi: printf("\nAria dreptunghiului este %f",
fig.figura.lat_drept[0]*fig.figura.lat_drept[1]);
break;
case Triunghi: for(p=0,i=0;i<3;i++)p+=fig.figura.lat_tr[i];
p=p/2;
for(i=0;i<3;i++)
if (p<=fig.figura.lat_tr[i])
{printf("\nValorile nu pot fi laturile unui triunghi");
return; }
for (aria=1,i=0;i<3;i++) aria*=(p-
fig.figura.lat_tr[i]);
aria=sqrt(p*aria);
printf("\nAria triunghiului este %f",aria);
break;
}
}

6.11.5. Câmpuri de biţi

Limbajul C permite utilizatorului definirea şi prelucrarea datelor pe biţi. Utilizarea


datelor pe biţi are legătură cu folosirea indicatorilor. Prin indicator se înţelege o dată care
poate avea doar două valori, 0 sau 1. O astfel de valoare se poate păstra în memorie şi pe un
Limbajul de programare C 216
singur bit. În acest scop limbajul C oferă posibilitatea de a declara date care să se aloce în
memorie pe biţi.
Se înţelege prin câmp un şir de biţi adiacenţi conţinuţi într-un cuvânt de memorie (un
cuvânt de memorie este de obicei, o locaţie de memorie de doi octeţi). Un câmp se declară ca
şi componentele unei structuri având unul dintre tipurile: unsigned, int, unsigned char sau
signed char, indicându-se în cadrul declaraţiei şi dimensiunea lui în număr de biţi.

O structură care are în componenţa sa câmpuri are forma:


struct {câmp1;
câmp2;
...
câmpn;
} nume;
unde câmp1, câmp2, ..., câmpn are unul din formatele:
unsigned nume: lungime_în_biţi;
sau :lungime_în_biţi;
Exemplu:
struct { unsigned x:1;
unsigned y: 2;
unsigned z: 3;
} indicatori;
Câmpurile din cadrul unei structuri pot fi referite ca şi celelalte componente ale
structurilor, ca în exemplul următor:
indicatori.x
indicatori.y
indicatori.z
Alocarea biţilor în memorie depinde de sistemul de calcul. În cazul limbajului
Borland C, biţii se alocă de la dreapta spre stânga (de la cei mai puţini semnificativi spre cei
mai semnificativi), ca în figura 6.7.
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0

x
y
z

Fig. 6.7. Modul de alocare în memorie a câmpurilor de biţi.


Observaţii:
217 Limbajul de programare C
1. Dacă un câmp nu se poate aloca într-un cuvânt de memorie, el se alocă în cuvântul
următor în întregime;
2. Un câmp nu poate avea dimensiunea mai mare de 16 biţi;
3. Cel de-al doilea format de declarare a unui câmp (în care nu se specifică numele
acestuia) se utilizează pentru cadraje, atunci când sunt zone de biţi neutilizate în cadrul unui
cuvânt;
4. O structură care are în componenţa sa câmpuri poate avea şi componente obişnuite;

5. Nu se pot defini tablouri de câmpuri;


6. Unui câmp nu i se poate aplica operatorul adresă (& ca operator unar) ;
7. Utilizarea câmpurilor poate avea ca efect obţinerea unui program cu o portabilitate
redusă.

6.11.6. Enumerări

Tipurile enumerare sunt introduse prin sintaxa:


enum nume {membrul,membru2,...} varl,var2,...;
De exemplu:
enum CULORI {ROSU,VERDE,ALBASTRU}
culoare_punct,culoare_linie;
enum CULORI culoare_cerc,culoare_fond;
defineşte tipul de date CULORI şi declară variabilele culoare_punct şi culoare_linie, urmate
de declarările a încă două variabile, culoare_cerc şi culoare_fond.
Membrii unui tip enumerat sunt numai de tip întreg. Valoarea fiecăruia este obţinută
prin incrementarea cu 1 a valorii membrului anterior, primul membru având, implicit, valoarea
0. Este permisă iniţializarea unui membru cu o valoare oarecare, avându-se în vedere că doi
membri ai aceluiaşi tip nu pot avea aceeaşi valoare. Valorile membrilor următori se vor stabili
conform regulilor menţionate.
enum ANOTIMP {IARNA=1, PRIMAVARA, VARA, TOAMNA};
enum BOOLEAN (fals,adevarat} conditie;
enum DIRECTIE {UP,DOWN,RIGHT,LEFT,NONE=0}; // ilegal
În ceea ce priveşte utilizarea variabilelor de tip enumerat limbajul C permite atribuiri
de tipul:
conditie=0;
dar acest tip de atribuiri generează avertismente din partea compilatorului. De aceea este bine
ca astfel de atribuiri să fie însoţite de conversia de tip corespunzătoare.
conditie=fals;
conditie=(enum BOOLEAN) 0;

6.11.7. Pointeri spre structuri


Limbajul de programare C 218

Pointerii sunt adrese de variabile de orice tip. Prin urmare, un pointer poate fi declarat
ca un pointer spre tipul structură.
Un pointer către o structură se declară similar cu pointerii către variabile simple.
Dacă TipStruct este un tip de date de tip structură definit explicit cu ajutorul construcţiei
typedef, declararea unui pointer spre structura aceea se face astfel:
TipStruct *P;

De asemenea, dacă tipul de date structură nu se defineşte explicit cu typedef, se poate


folosi definiţia cu struct :
struct Tipt*p;
Să considerăm un exemplu de structură:
typedef struct {
unsigned nr_mat;
char nume[20], adr[30];
struct { unsigned zi,luna,an; } dn;
float med_gen;
} Student;
Declararea unui pointer spre tipul Student se va face astfel :
Student *p;
În general, dacă p este un pointer spre o structură atunci *p este structura respectivă,
iar (*p).nume_mebru este un membru obişnuit al structurii. Pentru exemplul anterior avem
(*p).nume sau (*p).med_gen sau (*p).dn.zi .
Limbajul C permite o notaţie echivalentă pentru aceste construcţii: dacă p este un
pointer către o structură, iar m este un membru al acestei structuri, atunci (*p).m este
echivalentă cu notaţia p ->m (simbolul ->este format din caracterul - urmat de semnul >) .
Toate problemele referitoare la pointeri către variabile simple rămân valabile şi la
pointeri către structuri. De exemplu, va trebui să rezervăm loc pentru structura ce va fi
referită cu ajutorul unui pointer spre structuri.
Vom prezenta, mai departe, un exemplu de lucru cu pointeri spre structuri. Vom
considera următoarele date despre cele n cărţi ale bibliotecii facultăţii: numărul de inventar,
titlul, autorul şi anul intrării în bibliotecă. Vom folosi tablou de pointeri spre structuri (adică
mai mulţi pointeri spre structuri) pentru stocarea datelor. Se vor afişa datele despre cărţi,
sortate alfabetic după titlu, se vor număra cărţilor unui autor dat, se vor afişa cărţile intrate în
bibliotecă într-un an dat sau un mesaj corespunzător dacă nu sunt cărţi intrate în anul respectiv.
Sursa programului este prezentată în continuare.
#include <conio.h>
#include <stdio.h>
#include <alloc.h>
#include <string.h>
219 Limbajul de programare C
typedef struct carte{ unsigned long nr_inv;
char titlu[30], autor[20];
unsigned int an;
}Carte;
int N;
Carte *C[20]; // se declară 20 de pointeri spre structura carte

void Citire (void)


{ int I;
printf("\n Dati numarul de carti");
scanf("%d",&N);
for (I=0; I<N; I++)
C[I]=(Carte *)malloc(N * sizeof(Carte));//se aloca memorie pentru datele celor N carti
for (I=0; I<N ; I++)
{ printf("\n Dati numarul de inventar pentru cartea %d",I+1);
scanf("%d", &C[I]->nr_inv);
printf("\n Dati titlul pentru cartea %d",I+1);
scanf("%s", C[I]->titlu);
printf("\n Dati autorul pentru cartea %d",I+1);
scanf("%s", C[I]->autor);
printf("\n Dati anul intrarii in biblioteca pentru cartea %d",I+1);
scanf("%d", &C[I]->an); }
}
void Afisare_ordonate(Carte *C[20], int N)
{ int I, Ok;
Carte *Aux;
Aux=(Carte *)malloc(sizeof(Carte));
do
{ Ok=0;
for (I=0; I<N-1; I++)
if (strcmp(C[I]->titlu,C[I+1]->titlu)>0)
{ Aux=C[I];
C[I]=C[I+1];
C[I+1]=Aux;
Ok=1; }
}
while (Ok);
printf("\n Datele celor N carti sunt:");
printf("\n Nr_inv Titlu Autor An intrare");
Limbajul de programare C 220
for (I=0; I<N ; I++)
{ printf("\n%5d", C[I]->nr_inv);
printf(" %30s", C[I]->titlu);
printf(" %20s", C[I]->autor);
printf("%5d", C[I]->an); }
printf("\n"); }

int Numar (Carte *C[20],int N,char *Autor)


{ int I, Nr;
Nr=0;
for (I=0; I<N ; I++)
if (strcmp(Autor,C[I]->autor)==0) Nr++;
return Nr; }
void Afisare(Carte *C[20],int N,int An)
{ int I,k=0;
for (I=0; I<N ; I++)
if (C[I]->an==An)
{ printf("\n %s %s", C[I]->autor, C[I]->titlu);
k=1; }
if (k==0) printf("\n Nu exista carti intrate in anul %d", An); }
void main()
{ int I, optiune, An;
char *Autor;
Autor=(char *)malloc(20);
Citire();
do
{ printf("\n Dati 1- afisare carti ordonate dupa titlu");
printf("\n dati 2- numararea cartilor unui autor dat");
printf("\n dati 3- afisarea cartilor intrate intr-un an dat");
printf("\n dati 4- revenire in program");
scanf("%d", &optiune);
switch (optiune)
{ case 1: Afisare_ordonate(C , N);break;
case 2:{ printf("\n Dati autorul ");
scanf("%s", Autor);
printf("\n Numarul cartilor autorului %s este %d",Autor, Numar(C,N, Autor));
break; }
case 3: { printf("\n Dati anul ");
scanf("%d", &An);
221 Limbajul de programare C
Afisare(C,N, An);
break; }
}
}
while (optiune != 4);
for (I=0;I<N;I++) free(C[I]); // se elibereaza memoria folosită la stocarea cărţilor
}

6.12. Fişiere

Limbajul C nu are instrucţiuni de intrare/ieşire. Aceste operaţii se realizează prin


intermediul unor funcţii din biblioteca standard a limbajului C. Aceste funcţii pot fi aplicate în
mod eficient la o gamă de aplicaţii datorită multiplelor facilităţi pe care ele le oferă. Ele
asigură o portabilitate bună a programelor, fiind implementate într-o formă compatibilă sub
toate sistemele de operare. Aceasta nu înseamnă că ele nu au facilităţi specifice pe anumite
sisteme, cum este de exemplu cazul limbajului Borland C.
În acest capitol se vor face referiri la funcţiile utilizate mai frecvent în operaţiile de
intrare/ieşire. Aceste operaţii presupun că datele sunt organizate în fişiere.
În general, prin fişier înţelegem o colecţie ordonată de elemente numite înregistrări,
care sunt păstrate pe diferite suporturi externe (Dintre cele mai utilizate amintim
suporturile magnetice). Acestea, de obicei, sunt discuri şi benzi magnetice. Ele se numesc
suporturi reutilizabile, deoarece zona utilizată pentru a păstra înregistrările unui fişier
poate fi ulterior reutilizată pentru a păstra înregistrările altui fişier.
Datele introduse de la un terminal se consideră că formează un fişier de intrare.
Înregistrarea în acest caz, de obicei este formată din datele tastate la terminal pe un rând, deci
caracterul de rând nou (newline) este terminator de înregistrare. În mod analog, datele care se
afişează pe terminal formează un fişier de ieşire. Înregistrarea, şi în acest caz, poate fi formată
din caracterele unui rând.
Un fişier are o înregistrare care marchează sfârşitul de fişier. În cazul fişierelor de
intrare de la tastatură sfârşitul de fişier se generează prin Ctrl+Z. El poate fi pus în evidenţă
folosind constanta simbolică EOF definită în fişierul stdio.h.
Prelucrarea fişierelor implică un număr de operaţii specifice acestora. Orice fişier,
înainte de a fi prelucrat, trebuie să fie deschis. De asemenea, la terminarea prelucrării unui
fişier, acesta trebuie închis.
Operaţiile de deschidere şi închidere a unui fişier se pot realiza prin intermediul unor
funcţii speciale din biblioteca standard a limbajului C. Alte operaţii frecvente în prelucrarea
fişierelor sunt:
- crearea unui fişier; -consultarea unui fişier;
- actualizarea unui fişier; - adăugarea de înregistrări într-un
fişier;
Limbajul de programare C 222
- poziţionarea într-un 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 a limbajului C.
Prelucrarea fişierelor se poate face la două niveluri. Primul nivel face apel direct la
sistemul de operare. Acesta este nivelul inferior de prelucrare a fişierelor. Celălalt nivel se
realizează prin utilizarea unor proceduri specializate în prelucrarea fişierelor care, printre
altele, pot rezerva şi gestiona automat zone tampon necesare realizării operaţiilor de
intrare/ieşire. Acesta este nivelul superior de prelucrare a fişierelor.

6.12.1. Nivelul inferior de prelucrare a fişierelor

6.12.1.1. Deschiderea unui fişier

Orice fişier înainte de a fi prelucrat trebuie deschis. Deschiderea unui fişier existent se
realizează prin intermediul funcţiei open. La revenirea din ea se returnează aşa numitul
descriptor de fişier. Aceasta 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.
Prototipul acestei funcţii este:
int open (const char*cale, int acces);
unde cale este pointer spre un şir de caractere care defineşte calea spre fişierul care se
deschide iar acces o variabilă de tip întreg care poate lua una din valorile următoare:

O_RDNLY fişierul se deschide numai în citire (consultare);


O_WRONLY fişierul se deschide numai în scriere (creare);
O_RDWR fişierul se deschide în citire/scriere;
O_APPEND fişierul se deschide la sfârşit pentru adăugare de înregistrări;
O_BINARY fişierul se prelucrează binary;
O_TEXT fişierul este de tip text.

Aceste valori se pot combina cu ajutorul caracterului ‚’, de exemplu:


O_RDW O_BINARY fişierul este deschis în citire/scriere binară.
În mod implicit se consideră că fişierul este de tip text.
Utilizarea funcţiei open presupune că în prealabil s-au inclus fişierele io.h şi fcnt1.h
astfel:
#include <io.h>
#include <fcnt1.h>
Calea spre un fişier trebuie să respecte convenţiile sistemului de operare MS-DOS. În
cea mai simplă formă ea este un şir de caractere care definesc numele fişierului, urmat de
punct şi extensia fişierului. Aceasta presupune că fişierul respectiv se află în directorul curent.
223 Limbajul de programare C
În cazul în care fişierul nu este în directorul curent, numele este precedat de o
construcţie de forma litera: \nume_1\…\nume_k\ unde litera defineşte discul (în general A, B
pentru disc flexibil şi C, D pentru disc fix) iar nume_i este nume de subdirector (i=1,2,…,k).
Observaţie:
Deoarece calea se include între ghilimele, caracterul ‘\’ trebuie dublat.
Deschiderea unui fişier nu reuşeşte în cazul în care unul din parametric este eronat. În
acest caz funcţia open returnează valoarea –1.
De exemplu, dacă se încearcă închiderea unui fişier inexistent, funcţia open va returna
valoarea -1.

Exemple:
1) char nfis[ ] = “fis1.dat”;
int df;

df = open(nfis, O_RDONLY);

Prin apelul de mai sus se deschide în citire fişierul fis1.dat din directorul curent.
Descriptorul de fişier returnat de funcţia open se atribuie variabilei df.
2) char fis = “A:\\JOC\\BIO.C”;
int d;
...
d = open (fis, O_RDWR);
...
Prin acest apel se deschide în citire/scriere fişierul BIO.C din directorul JOC aflat pe
discul flexibil încărcat pe unitatea A.
3) int d;
d = open (“c:\\tc\\include\\text.h”, O_APPEND)
Se deschide în adăugare fişierul text.h din subdirectorul include al directorului tc de pe
discul C.
Observaţie:
La compilare şirul de caractere utilizat ca parametru se va păstra într-o zonă de date şi
el este înlocuit prin adresa zonei în care s-a memorat şirul respectiv.
Pentru a crea un fişier nou se va folosi funcţia creat în locul funcţiei open. Ea are
prototipul:
int crea (const char*cale, int mod);
unde cale este un parametru care se defineşte ca şi în cazul funcţiei open iar mod este un
întreg care poate fi definit prin constantele simbolice de mai jos:
S_IREAD proprietarul poate citi fişierul;
S_IWRITE proprietarul poate scrie în fişier;
Limbajul de programare C 224
S_IEXEC proprietarul poate executa programul conţinut în fişier.
Aceşti indicatori pot fi combinaţi folosind caracterul ‚’. De exemplu, pentru
citire/scriere se va folosi:
S_IREAD S_IWRITE proprietarul poate citi/scrie fişierul.
Observaţii:
1) Funcţia creat returnează descriptorul de fişier sau –1 în caz de eroare.
2) Utilizarea acestei funcţii implică includerea fişierelor io.h şi stat.h.
3) Funcţia creat poate fi utilizată şi pentru a deschide un fişier existent. În acest caz
vechiul fişier se şterge şi în locul lui se va crea unul nou cu acelaşi nume.

6.12.1.2. Citirea dintr-un fişier (consultare)

Operaţia de citire a unei înregistrări dintr-un fişier deschis în prealabil cu funcţia open
se realizează cu ajutorul funcţiei read. Ea returnează numărul octeţilor citiţi din fişier.
Prototipul acestei funcţii este
int read(int df, void*buf, unsigned lung);
unde:
- df este descriptorul de fişier a cărui valoare a fost definită la apelul funcţiei open
pentru fişierul respectiv;
- buf este pointerul spre zona de memorie în care se recepţionează înregistrarea care se
citeşte;
- lung este lungimea în octeţi a înregistrării citite.
La eroare, funcţia read returnează valoarea –1. La fiecare apel al funcţiei read se
citeşte înregistrarea curentă. Astfel, la primul apel se citeşte prima înregistrare din fişier, la al
doilea apel a doua înregistrare şi aşa mai departe. Ordinea înregistrărilor este cea definită la
crearea fişierului şi eventual la adăugarea de înregistrări după crearea lui. La un apel al
funcţiei read se citesc cel mult lung octeţi, înregistrarea având lungimea definită la scrierea ei
în fişier. La sfârşit de fişier nu se citeşte nimic şi deci funcţia read returnează valoarea zero.
Dacă lung = 1, atunci 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. De aceea se folosesc frecvent înregistrări de 512 octeţi sau
mai mari.
Funcţia read poate fi folosită pentru a citi de la intrarea standard (de la terminalul de
la care s-a lansat programul). În acest caz, descriptorul de fişier este 0. Programatorul nu
trebuie să deschidă acest fişier deoarece el este deschis automat la lansarea în execuţie a
programului.
Utilizarea funcţiei read presupune includerea fişierului io.h.

6.12.1.3. Scrierea într-un fişier (creare, punere la zi, adăugare)


225 Limbajul de programare C
Pentru a scrie o înregistrare într-un fişier vom folosi funcţia write. Se presupune că
fişierul este deschis în prealabil prin funcţia creat sau open.
Ea este asămănătoare cu funcţia read, doar că realizează transferul de date în sens
invers, adică din memorie în fişier. Ea are prototipul:
int write(int df, void*buf, unsigned lung);
Funcţia returnează numărul octeţilor scrişi în fişier. Acesta este egal cu lung şi
defineşte lungimea înregistrării scrise în fişier. În cazul în care numărul returnat de funcţia
write diferă de parametrul lung scrierea a fost eronată.
Funcţia write poate fi utilizată pentru a scrie la ieşirile standard stdout şi stderr.

Astfel, pentru a scrie la ieşirea stdout se utilizează descriptorul_de_fişier = 1, iar


pentru stderr descriptorul_de_fişier = 2. Aceste fişiere nu trebuie deschise de programator
deoarece ele se deschid automat la lansarea programului.
Utilizarea funcţiei write implică includerea fişierului io.h.

6.12.1.4. Poziţionarea într-un fişier

Aşa cum s-a arătat mai sus, operaţiile de citire sau scriere într-un fişier se execută
secvenţial, adică la fiecare apel al funcţiei read sau write se citeşte înregistrarea curentă,
respectiv se scrie înregistrarea în poziţia curentă de pe suportul fişierului. Acest mod de acces
la fişier se numeşte secvenţial şi el este util când dorim să prelucrăm fiecare înregistrare a
fişierului, una după alta. În practică apar însă şi restricţii în care dorim să scriem şi să citim
înregistrări într-o ordine aleatoare. În acest caz se spune că accesul la fişier este aleator. Pentru
a se realiza un acces aleator este nevoie să ne putem poziţiona oriunde în fişierul respectiv. O
astfel de poziţionare este posibilă pe suporturile magnetice de tip disc şi se realizează folosind
funcţia lseek. Ea are prototipul:
long lseek(int df, long deplasament, int origine);
unde:
- df este descriptorul de fişier;
- deplasament defineşte numărul de octeţi peste care se va deplasa capul de
citire/scriere al discului;
- origine are una din valorile:
0 deplasamentul se consideră de la începutul fişierului;
1 deplasamentul se consideră din poziţia curentă a capului de citire/scriere;
2 deplasamentul se consideră de la sfârşitul fişierului.
Observaţii:
1) 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 a capului de citire/scriere.
Limbajul de programare C 226
2) Funcţia returnează poziţia capului de citire/scriere faţă de începutul fişierului, în
număr de octeţi. În caz de eroare se returnează 1L.
3) Utilizarea funcţiei lseek presupune includerea fişierului io.h.
4) Apelul lseek(df, 01, 2) permite o poziţionare la sfârşitul fişierului al cărui descriptor
este df. În mod analog, apelul lseek(df,01,0) permite o poziţionare la început de fişier.

6.12.1.5. Închiderea unui fişier

După terminarea prelucrării unui fişier acesta trebuie închis. Acest lucru se realizează
automat dacă programul se termină prin apelul funcţiei exit. Programatorul poate închide un
fişier folosind funcţia close. Se recomandă închiderea unui fişier de îndată ce s-a terminat

prelucrarea lui. Aceasta din cauză că numărul fişierelor ce pot fi deschise simultan este
limitat. Această limită este dependentă de sistemul de operare şi ea variază, de obicei, în
intervalul 15-25.
Observaţie:
Fişierele corespunzătoare intrărilor şi ieşirilor standard nu se închid de către
programator.
Funcţia close are prototipul:
int close(int df);
unde df este descriptorul fişierului care se închide. La o închidere normală, funcţia returnează
valoarea 0. În caz de incident se returnează valoarea –1. Utilizarea funcţiei close implică
includerea fişierului io.h.
Exemple:
1. Programul următor copiază intrarea standard la ieşirea standard folosind o zonă
tampon de 70 de caractere.
#define LZT 70
#include <io.h>
void main() /* copiaza intrarea standard la
ieşirea standard */
{
char zt[LZT];
int 1;
while(1 = read (0, zt, LZT)) > 0) write(1, zt, 1);
}

2. Programul următor citeşte un şir de numere flotante de la intrarea standard şi


crează două fişiere fis1.dat şi fis2.dat. Primul conţine numerele de ordin impar citite de la
intrarea standard, iar cel de-al doilea conţine numerele de ordin par. Apoi se listează la
227 Limbajul de programare C
ieşirea standard stdout cele două fişiere în ordinea fis1.dat, fis2.dat, câte un număr pe un rând
în formatul număr de ordine număr.
În acest program se utilizează funcţia standard exit, care întrerupe execuţia unui
program. Ea are ca parametru un număr întreg, care este egal cu zero la o terminare normală a
programului şi cu o valoare diferită de zero în caz de eroare, care reprezintă codul de eroare.

#include <stdio.h>
#include <stat.h>
#include <fcnt1.h>
#include <io.h>
void main () /* citeste un sir de numere flotante si creează
fis1.dat cu numerele de ordin impar si fis2.dat cu
numerele de ordin par; în final se listeaza fisierele */

{ union unr
{ float nr;
char tnr [sizeof(float)]; };
union unr nrcit;
int df1, df2;
int i, j;
/*se deschid fisierele in creare cu acces in citire/scriere*/
if ((df1 = creat(“fis1.dat”, S_IWRITES_IREAD)) == -1)
{ printf(“nu se poate deschide fisierul fis1.dat in creare \n”);
exit(1); }
if ((df2 = creat(“fis2.dat”, S_IWRITES_IREAD)) == -1)
{ printf(“nu se poate deschide fisierul fis2.dat in creare \n”);
exit(1); }
/* se citeste sirul de numere si se pastreaza in cele doua fisiere */
j = 1;
while ((i = scanf(“%f”, &nrcit.nr)) == 1)
{ if (j%2 == 1)
{ /*se scrie in fisierul fis1.dat deoarece j este impar*/
if (( write (df1, nrcit.tnr, sizeof(float)))!= sizeof (float))
{ printf(“eroare la scriere in fisierul fis1.dat\n”);
exit(1); }
}
else /* se scrie in fisierul fis2.dat fiindca j este par */
if ((write(df2, nrcit.tnr, sizeof(float)))!= sizeof(float))
{ printf(“eroare la scriere in fisierul fis2.dat\n”);
exit(1); }
Limbajul de programare C 228
j++;
} /* sfarsit while */
if (close(df1) < 0) /* se inchid fisierele */
{ printf(“eroare la inchiderea fisierului fis1.dat\n”);
exit(1); }
if ((df1 = open (“fis1.dat”, O_RDONLY)) == -1)
{ printf(“nu se poate deschide fisierul fis1.dat in citire\n”);
exit(1); } /* se deschide fisierul fis1.dat in citire */
j = 1; /* se listeaza fisierul fis1.dat */
while((i = read(df1, nrcit.tnr, sizeof(float))) > 0)
{ printf(“%6d %g\n”, j, nrcit.nr);
j + = 2; }

if (i < 0)
{ printf(“eroare la citire din fis1.dat\n”);
exit(1); }
close(df1);
if ((df2 = open(“fis2.dat”, O_RDONLY) == -1)
{ printf(“nu se poate deschide fis2.dat in citire\n”);
exit(2); } /* se deschide fisierul fis2.dat */
printf(“n\n\n”); /* se listeaza fisierul fis2.dat */
j = 2;
while((i = read (df2, nrcit.tnr, sizeif (float))) > 0)
{ printf(“%6d %g\n”, j, nrcit.nr);
j + = 2; }
if (i < 0)
{ printf(“eroare la citire din fisierul fis2.dat\n”);
exit(1); }
close(df2);
}
Observaţie:
nrcit este o reuniune căreia i se alocă 4 octeţi, adică o dimensiune capabilă să păstreze
un număr flotant în simplă precizie. Zona respectivă este organizată şi ca un tablou de tip
char, deoarece funcţiile read şi write necesită o zonă tampon de acest tip la majoritatea
implementărilor limbajului C.

6.12.2. Nivelul superior de prelucrare a fişierelor

6.12.2.1. Deschiderea unui fişier


229 Limbajul de programare C

La acest nivel se utilizează funcţia fopen pentru deschiderea unui fişier. Ea returnează un
pointer spre tipul FILE (tipul fişier), tip definit în fişierul stdio.h. Tipul FILE este un tip
structurat şi el depinde de sistemul de operare. În caz de eroare funcţia fopen returnează
pointerul NULL.
Prototipul funcţiei fopen este:
FILE *fopen(const char cale, const charmod);
unde:
cale – are aceeaşi semnificaţie ca şi în cazul funcţiilor open şi creat.
mod – este un pointer spre un şir de caractere care defineşte modul de prelucrare al
fişierului după deschidere.
Acest şir se defineşte în felul următor:
„r” – deschidere în citire(read);

„w” – deschidere în scriere(write);


„a” – deschidere pentru adăugare (append);
„r+” – deschidere pentru modificare(citire sau scriere);
„rb” – citire binară;
„wb” – scriere binară;
„r+b” – citire/scriere binară.
Dacă se deschide un fişier inexistent f.cpp modul „w” sau „a”, atunci el este deschis în
creare. Dacă se deschide un fişier existent cu modul „w”, atunci conţinutul vechi al fişierului
se pierde şi se crează unul nou cu acelaşi nume.
Menţionăm că stdin, stdout, stderr, stdaux şi stdprn sunt pointeri spre tipul FILE şi
permit ca funcţiile de nivel superior de prelucrare a fişierelor să poată trata intrarea standard
şi ieşirile standard pe terminal şi imprimantă la fel ca şi fişierele pe celelalte suporturi.
Singura deosebire constă în aceea că aceste fişiere nu se deschid şi nici nu se închid de către
programator. Ele sunt deschise automat la lansarea în execuţie a programului şi se închid la
apelul funcţiei exit.

6.12.2.2. Prelucrarea pe caractere a unui fişier

Fişierele pot fi scrise şi citite caracter cu caracter, folosind două funcţii simple şi
anume putc pentru scriere şi getc pentru citire. Funcţia putc are prototipul:
int putc(int c, FILE *pf)
unde:
- c - este codul ASCII al caracterului care se scrie în fişier;
- pf - este pointerul spre tipul FILE a cărui valoare a fost returnată de funcţia
fopen la deschiderea fişierului în care se scrie. În particular, pf poate fi unul din
pointerii:
Limbajul de programare C 230
stdout ieşire standard;
stderr ieşire pe terminal în caz de eroare;
stdaux comunicaţie serială;
stdprn ieşire paralelă la imprimantă.
Funcţia putc returnează valoarea lui c respectiv –1 în caz de eroare.
Funcţia getc are prototipul:
int getc(FILE *pf);
unde pf este pointerul spre tipul FILE a cărui valoare a fost returnată de funcţia fopen la
deschiderea fişierului. Ea returnează codul ASCII a caracterului citit sau EOF la sfârşit de
fişier sau eroare. În particular, pf poate fi pointerul stdin (intrare de la tastatură).

6.12.2.3. Închiderea unui fişier

După terminarea prelucrării unui fişier, aesta urmează a fi închis. În acest caz se
utilizează funcţia fclose. Ea are prototipul:
int fclose(FILE *pf);

unde pf este pointerul spre tipul FILE a cărui valoare a fost definită la deschiderea fişierului
prin intermediul funcţiei fopen. Funcţia fclose returnează:
0 la închiderea normală a fişierului;
1 în caz de eroare.
Exemple:
1. Programul copiază intrarea standard la ieşirea standard stdout, folosind funcţiile
getc şi putc.

#include <stdio.h>
void main( ) /*copiaza intrarea stdin la ieşirea stdout */
{ int c;
while((c = getc(stdin)) ! = EOF) putc(c, stdout);
}
Observaţie:
Macrourile getchar şi putchar sunt definite în fişierul stdio.h astfel:
#define getchar ( ) .getc(stdin)
#define putchar(c) putc(c, stdout)
2. Programul următor copiază intrarea standard la imprimantă.
#include <stdio.h>
void main ( ) /*copiază caracterele din stdin la imprimanta */
{ int c;
while ((c = getc(stdin)) != EOF) putc(c, stdprn);
}
231 Limbajul de programare C
3. Programul următor scrie la ieşirea stdout caracterele unui fişier a cărui cale este
argumentul din linia de comandă. Dacă nu există un argument în linia de comandă, atunci
se citeşte de la intrarea standard.
#include <stdio.h>
void main(argc, argv) /* listeaza la iesirea stdout un fisier a carui cale
este argumentul din linia de comanda */
int argc;
char *argv [ ];
{ FILE *pf;
int c;
if(argc == 1) /* nu exista argument in linia de comanda */
pf = stdin;
else /* se deschide fisierul a carui cale se afla in argv [1] */
if((pf = fopen (* ++ argv, “r”)) == NULL)
{ printf(“nu se poate deschide fisierul %s\n”, *argv);
exit(1); }

while((c = getc(pf)) != EOF) putchar (c);


exit(0); /* se inched automat fisierele deschise */

6.12.2.4. Operaţiile de intrare/ieşire cu format

Biblioteca standard a limbajului C conţine funcţii care permit realizarea operaţiilor de


intrare/ieşire cu format. Se pot utiliza funcţiile fscanf şi fprintf, prima pentru citire cu
format dintr-un fişier, iar a doua pentru scriere cu format într-un fişier.
Funcţia fscanf este asemănătoare cu funcţia scanf. Ea are un parametru în plus faţă de
scanf, un pointer spre tipul FILE care defineşte fişierul din care se face citirea. Acest
pointer este primul parametru al funcţiei fscanf. Ceilalţi parametrii au aceeaşi semnificaţie
ca şi în cazul funcţiei scanf.
Rezultă că funcţia fscanf poate fi apelată printr-o expresie de atribuire de forma:
nr = fscanf (pf, control, par1, par2, …, parn);
unde pf este pointer spre tipul FILE şi valoarea lui a fost definită prin apelul funcţiei
fopen (acesta defineşte fişierul din care se face citirea), iar ceilalţi parametri sunt identici
cu cei utilizaţi la apelul funcţiei scanf.
Funcţia fscanf, ca şi scanf, returnează numărul câmpurilor citite din fişier. La
întâlnirea sfârşitului de fişier se returnează valoarea EOF definită în fişierul stdio.h. Pentru
pf= stdin, funcţia fscanf este identică cu scanf.
Funcţia fprintf este analogă cu funcţia printf. Primul ei parametru este un pointer spre
tipul FILE şi el defineşte fişierul în care se scriu datele. Ceilalţi parametrii sunt identici cu
cei ai funcţiei printf.
Limbajul de programare C 232
Funcţia fprintf, ca şi funcţia printf, returnează numărul caracterelor scrise în fişier sau
–1 caz de eroare. Pentru pf = stdout, funcţia fprintf este identică cu printf. De asemenea,
se pot utiliza pointerii standard obişnuiţi:
stderr – afişarea mesajelor de eroare pe terminal;
stdaux – comunicaţie serială;
stdprn – ieşirea paralelă la imprimantă.
Menţionăm că la comunicaţia serială se poate conecta o imprimantă serială.

6.12.2.5. Intrări/ieşiri de şiruri de caractere

Biblioteca standard a limbajului C conţine funcţiile fgets şi fputs care permit citirea
respective scrierea într-un fişier ale cărui înregistrări sunt şirurile de caractere.
Funcţia fgets are prototipul:
char*fgets(char*s, int n, FILE*pf);
unde: - s este pointerul spre zona în care se face citirea caracterelor;
- n – 1 este numărul maxim de caractere care se citesc iar pf este pointerul spre
tipul FILE care defineşte fişierul din care se face citirea.

De obicei s este numele unui tablou de tip char de dimensiune cel puţin n. Dacă şirul se
termină cu ”\n”, citirea se opreşte. În acest caz, în zona receptoare se transferă caracterul
”\n” şi apoi caracterul NUL(”\0”). În mod normal, funcţia returnează valoarea pointerului
s. La întâlnirea sfârşitului de fişier se returnează valoarea NULL.
Funcţia fputs scrie într-un fişier un şir de caractere care se termină prin ”\n”. Ea are
prototipul:
int fputs(const char *s, FILE *pf);
unde: - s este pointerul spre zona care conţine şirul de caractere care se scrie;
- pf este pointerul spre tipul FILE care defineşte fişierul în care se scrie.
Funcţia fputs returnează codul ASCII al utilimului caracter scris sau –1 în caz de eroare.
Aceste funcţii sunt realizate folosind funcţia getc pentru fgets şi putc pentru fputs.
Pentru a citi de la intrarea standard stdin, se poate folosi funcţia gets, care nu mai are
parametrii pf şi n. Parametrul pf este implicit stdin. Funcţia gets citeşte caracterele de la
intrarea standard până la întâlnirea caracterului ”\n” care nu mai este păstrat în zona spre
care pointează s. Şirul de caractere citit se termină şi în acest caz cu ”\0”.
În mod analog, pentru a scrie la ieşirea standard se poate folosi funcţia puts, care nu mai
are parametrul pf, acesta fiind implicit stdout. În rest, funcţia puts este la fel ca şi funcţia
fputs.

6.12.2.6. Poziţionarea într-un fişier


233 Limbajul de programare C
Biblioteca standard a limbajului C conţine funcţia fseek, cu ajutorul căreia se poate
deplasa capul de citire/scriere al discului în vederea prelucrării înregistrărilor fişierului
într-o ordine oarecare, diferită de cea secvenţială (acces aleator). Această funcţie este
asemănătoare cu funcţia lseek. Ea are prototipul:
int fseek (FILE * pf,long deplasament, int origine);
unde pf este pointerul spre tipul FILE care defineşte fişierul în care se face poziţionarea
capului de citire/scriere iar deplasament şi origine au aceeaşi semnificaţie ca şi în cazul
funcţiei lseek.
Funcţia fseek returnează valoarea zero la poziţionare corectă şi o valoare diferită de zero în
caz de eroare.
O altă funcţie utilă în cazul accesului aleator este funcţia ftell, care indică poziţia capului
de citire în fişier. Ea are prototipul:
long ftell (FILE * pf);
unde pf este pointerul spre tipul FILE care defineşte fişierul în cauză.
Funcţia returnează o valoare de tip long care defineşte poziţia curentă a capului de
citire/scriere şi anume reprezintă deplasamentul în octeţi a poziţiei capului faţă de
începutul fişierului.

6.12.2.7. Prelucrarea fişierelor binare

Fişierele organizate ca date binare (octeţii nu sunt consideraţi ca fiind coduri de caractere)
pot fi prelucrate la acest nivel folosind funcţiile fread şi fwrite.
În acest caz, se consideră că înregistrarea este o colecţie de date structurate numite
articole. La o citire se transferă într-o zonă specială, numită zonă tampon, un număr de
articole care se presupune că au o lungime fixă. În mod analog, la scriere se transferă din
zona tampon un număr de articole de lungime fixă. Cele două funcţii au prototipurile de
mai jos:
unsigned fread (void * ptr, unsigned dim, unsigned nrart, FILE * pf);
unsigned fwrite (const void * ptr, unsigned dim, unsigned nrart, FILE * pf);
unde:
- ptr este pointerul spre zona tampon ce conţine articole citite (înregistrarea citită);
- dim este un întreg ce reprezintă lungimea unui articol;
- nrart este un întreg ce reprezintă numărul de articole ce se transferă;
- pf este un pointer spre tipul FILE care defineşte fişierul din care se face citirea.
Ambele funcţii returnează numărul de articole transferate sau –l în caz de
eroare.
Exemple:
1. Programul citeşte de la intrarea stdin datele ale căror formate sunt definite mai jos şi le
scrie în fişierul misc.dat din directorul curent. Formatul datelor de intrare este următorul
Limbajul de programare C 234
tip denumire um cod pret cantitate
I REZISTENTA010KO 123456789 1.5 10000.0

#include <stdio.h>
#define MAX 50
typedef struct { char tip [2];
char den [MAX +1];
int val;
char unit[3];
long unit [3]
float pret;
float char; }
ARTICOL;
union {ARTICOL a[6]; /* 6 articole in zona tampon */
} buf;
int cit (ARTICOL *str)
// citeste datele de la intrarea standard si le scrie in structura spre care pointeaza str
{ int c, nr;
float x,y;

while ((nr = scanf(“%1s%50s%3d&2s%1d”, str → tip,


str → den, &str → val, str → unit, & str → cod)) !=
if(nr ==EOF) return EOF;
printf(“rand eronat; se reia \n”);
while ((c=getchar())!=‚\n’&&c!=EOF);/*avans pana la newline sau EOF*/
if (c ==EOF) return EOF; } /*sfarsit while */
str → pret = x; str → cant = y;
} /* sfarsit cit */
void main ()
/* creeaza fisierul misc.dat cu datele citite de la stdin */
{ FILE *pf;
ARTICOL a;
int i, n;
/* se deschide fisierul in scriere binara */
if ((pf = fopen (“misc.dat”, „wb”)) ==NULL)
{ printf(“nu se poate deschide in creare fisierul misc.dat\n”);
exit(1); }
for ( ; ;) /* se umple zona tampon a fisierului */
{ for (i = 0; i < 6; i++) {
235 Limbajul de programare C
if ((n = cit (&a)) ==EOF) break;
buf.a[i] = a; }
if (i !=0) /*se scrie zona tampon daca nu este vida*/
if (i !=fwrite (buf.zt, sizeof(ARTICOL), i, pf))
{ /*eroare la scrierea in fisier*/
printf(“eroare la scrierea in fisier\n”);
exit(1); }
if (n == EOF) break;
} /* sfarsit for */
fclose(pf);
} /* sfarsit main */

Observaţie:
În TURBO C se pot face atribuiri de structuri, spre deosebire de unele versiuni ale
limbajului C care nu admit acest lucru. Deci atribuirea buf.a[i] = a; utilizată anterior este
corectă.

2. Programul următor listează articolele fişierului misc.dat, creat prin programul anterior,
pentru care tip = I.

#include<stdio.h>
#define MAX 50
typedef struct { char tip[2];
char den[MAX + 1];
int val;
char unit[3];
long cod;
float pret;
float cant;
}
ARTICOL;
union { ARTICOL a[6];
char zt[6*sizeof (ARTICOL)]; } buf;
void main () /* citeste fisierul misc.dat */
{ FILE *pf;
int i,n; ARTICOL a;
if ((pf = fopen(“misc.dat”, “rb”)) == NULL)
{ printf(“nu se poate deschide fisierul misc.dat in citire\n”);
exit(1); }
Limbajul de programare C 236
printf(“%60s\n\n\n”, “INTRARI\n\n”); /* se citeste o inregistrare */
while ((n = fread (buf.zt, sizeof(ARTICOL), 6, pf )) > 0)
/* se listeaza articolele de tip I dintr-o inregistrare */
for (i = 0; i < n; i++)
{ a = buf.a[i];
if (a.tip[0] == ‘I’)
printf (“%-50s %03d%s %91d %.2f %.2f\n”,
a.den, a.val, a.unit, a.cod, a.pret, a.cant);
}
fclose(pf);
}

6.12.2.8. Ştergerea unui fişier

Un fişier poate fi şters apelând funcţia unlink. Aceasta are prototipul:


int unlink(const char *cale);
unde cale este un pointer spre un şir de caractere identic cu cel utilizat la crearea fişierului
în funcţia creat sau fopen.
Funcţia unlink returnează valoarea zero la o ştergere, respective –1 în caz de eroare.

6.13. Realizarea programelor din surse multiple

6.13.1. Introducere
Scrierea unui program complex, care să respecte regulile programării structurate,
presupune împărţirea programului în module. Folosirea modulelor pentru structurarea
programului oferă mai multe avantaje, motiv pentru care este acceptată ca o metodă de lucru
generală în scrierea programelor. În primul rând, dezvoltarea aplicaţiei se poate face în echipă,
fiecare membru al echipei realizând anumite module ale aplicaţiei. În al doilea rând, aplicaţia
poate fi depanată şi adaptată mai uşor. De asemenea, codul din aceste module poate fi refolosit
şi în alte aplicaţii.
Modulele sunt, de obicei, funcţii C, având diverse scopuri. Funcţiile C sunt folosite
pentru a împărţi anumite părţi ale programului şi pentru construirea de funcţii de calcul. Există
două metode de lucru, pentru construirea programului. Prima metodă, constă în scrierea
acestor funcţii în unul sau mai multe fişiere sursă C şi încluderea lor în fişierul principal ce
conţine funcţia main(), folosind directiva de compilare #include. A doua metodă, constă în
folosirea proiectelor C pentru gruparea fişierelor sursă şi a bibliotecilor folosite în program şi
realizarea aplicaţiei finale prin intermediul proiectului.
237 Limbajul de programare C
Fiecare metodă are avantaje şi dezavantaje. Prima se poate folosi pentru aplicaţii
simple, nu foarte mari. În schimb, a doua metodă este recomandată pentru aplicaţii ce necesită
cod mult (şi timp de dezvoltare mare). Le vom prezenta în detaliu, în continuare:

6.13.2. Realizarea programului prin includerea fişierelor sursă prin


intermediul directivei #include

Această metodă este folosită, de obicei, în faza de învăţarea a unui limbaj sau pentru
realizarea unei aplicaţii simple. Programatorul îşi scrie codul funcţiilor în unul sau mai multe
fişiere sursă. Dacă programul nu are foarte multe funcţii, aceste funcţii se depun într-un singur
fişier sursă, fişier ce va conţine şi funcţia main(). Eventual, dacă programatorul defineşte
tipuri de date, poate construi un fişier cu extensia h (numit header) în care să depună
definiţiile de tipuri şi prototipurile funcţiilor din fişierul sursă, header ce se include şi el în
fişierul sursă. În schimb, dacă programul are multe funcţii, cu funcţiuni diferite, ele se pot
scrie în unul sau mai multe fişiere sursă C, pe care să le includă în fişierul cu funcţia main().
Vom considera un exemplu practic pentru a putea surprinde elementele de bază ale
acestei metode. Să presupunem că dorim realizarea unui program care să permită operarea cu
structuri de date de tip vector. Pentru aceasta, fişierul sursă va trebui să conţină funcţii pentru
citire, afişare, ordonare şi pentru calculul sumei elementelor vectorului. Soluţia va consta în
realizarea unui singur fişier sursă, care să conţină rutinele necesare operării cu strucura de tip
vector şi funcţia main().
Programul va arăta astfel :

#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
// Se scriu prototipurile funcţiilor ce se folosesc în program, eventual la fiecare
// funcţie se scrie sub formă de comentariu la ce foloseşte
void Citire(int X[],int &N);
void Afisare(int X[],int N);
void Ordonare(int X[],int N);
long Suma(int X[],int N);
void Citire(int X[],int &N)
{ int i;
printf("\nDati numarul de elemente ale vectorului:");
scanf("%d",&N);
for (i=0;i<N;i++)
{ printf("X[%d]=",i);
scanf("%d",&X[i]);
}
Limbajul de programare C 238
}
void Afisare(int X[],int N)
{ int i;
printf("\nVectorul ");
for(i=0;i<N;i++)
printf("\n%d",X[i]);
}
void Ordonare(int X[],int N)
{ int i,k,temp;
do
{ k=0;
for(i=0;i<N-1;i++)
if(X[i]>X[i+1])
{ temp=X[i];
X[i]=X[i+1];
X[i+1]=temp;
k=1;
}
}
while (k!=0);
}

long Suma(int X[],int N)


{ int i;
long S;
for(S=0,i=0;i<N;i++)
S+=X[i];
return S;
}
void main()
{ int Optiune,Y;
int X[100],N;
do
{ clrscr();
printf("\n 1-Citire");
printf("\n 2-Afisare");
printf("\n 3-Calcul suma");
printf("\n 4-Ordonare");
printf("\n 5-Iesire");
239 Limbajul de programare C
printf("\nAlegeti optiunea ");
scanf("%d",&Optiune);
switch(Optiune)
{ case 1 :Citire(X,N); break;
case 2 :Afisare(X,N); break;
case 3 :printf("\nSuma elementelor este %ld",Suma(X,N));
break;
case 4 :Ordonare(X,N); break;
}
getch();
}
while ( Optiune!=8);
}
În schimb, dacă aplicaţia de operare cu structura de tip vector mai necesită şi alte
funcţii, ca de exemplu de determinarea minimului din vector, determinarea numărului de
elemente cu o anumită proprietate, căutarea unui element, sau dacă se doreşte utilizarea şi în
alte programe a acestor funcţii, programatorul va trebui să “spargă” programul în două fişiere
sursă: un fişier conţinând codul acestor funcţii şi fişierul principal conţinând funcţia main() (şi
eventual şi alte funcţii), în care să folosească funcţiile definite. Varianta aceasta conţine
următoarele fişiere:
•Fişierul sursă, fişier ce conţine codul funcţiilor, să-i spunem funcţii.cpp este următorul:

#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
void Citire(int X[],int &N);
void Afisare(int X[],int N);
void Ordonare(int X[],int N);
long Suma(int X[],int N);
int Minim(int X[],int N);
int NumararePoz(int X[],int N);
int Cautare(int X[],int N,int Y);
void Citire(int X[],int &N)
{ int i;
printf("\nDati numarul de elemente ale vectorului:");
scanf("%d",&N);
for (i=0;i<N;i++)
{ printf("X[%d]=",i);
scanf("%d",&X[i]); }
Limbajul de programare C 240
}
void Afisare(int X[],int N)
{ int i;
printf("\nVectorul ");
for(i=0;i<N;i++)
printf("\n%d",X[i]);
}
void Ordonare(int X[],int N)
{ int i,k,temp;
do
{ k=0;
for(i=0;i<N-1;i++)
if(X[i]>X[i+1])
{ temp=X[i];
X[i]=X[i+1];
X[i+1]=temp;
k=1;
}
}
while (k!=0);
}

long Suma(int X[],int N)


{ int i;
long S;
for(S=0,i=0;i<N;i++)
S+=X[i];
return S;
}
int Minim(int X[],int N)
{ int i;
int Min;
Min=X[0];
for(i=0;i<N;i++)
if (Min>X[i])
Min=X[i];
return Min;
}
int NumararePoz(int X[],int N)
241 Limbajul de programare C
{ int i;
int Nr;
Nr=0;
for(i=0;i<N;i++)
if (X[i]>0)
Nr++;
return Nr;
}
int Cautare(int X[],int N,int Y) //intoarce pozitia sau -1 in ca ca nu gaseste
{ int i;
for(i=0;i<N;i++)
if (X[i]==Y)
return 1;
return -1;
}
•Al doilea fişier, fişierul principal ce foloseşte funcţiile prin intermediul proiectului,
să-i spunem princip.cpp arată astfel:

#include <stdio.h>
#include <conio.h>
#include "funcţii.cpp" // se include fişierul sursă, existent în directorul curent
void main()
{ int Optiune,Y;
int X[100],N;
do
{ clrscr();
printf("\n 1-Citire");
printf("\n 2-Afisare");
printf("\n 3-Calcul suma");
printf("\n 4-Ordonare");
printf("\n 5-Minim elemente");
printf("\n 6-Numarare elemente pozitive");
printf("\n 7-Cautare pozitie element");
printf("\n 8-Iesire");
printf("\nAlegeti optiunea ");
scanf("%d",&Optiune);
switch(Optiune)
{
case 1 :Citire(X,N);
Limbajul de programare C 242
break;
case 2 :Afisare(X,N);
break;
case 3 :printf("\nSuma elementelor este %ld",Suma(X,N));
break;
case 4 :Ordonare(X,N);
break;
case 5 :printf("\nMinimul elementelor este %d",Minim(X,N));
break;
case 6 :printf("\nNumarul elementelor pozitive este %d",
NumararePoz(X,N));
break;
case 7 :printf("\Dati elementul pe care il cautati");scanf("%d",&Y);
printf("\nPozitia elementului este %d",Cautare(X,N,Y));
break;
}
getch();
}
while ( Optiune!=8);
}
Această variantă este mult mai flexibilă decât varianta cu un singur fişier sursă. De
exemplu, puteţi refolosi fişierul sursă şi în alte aplicaţii, doar prin includerea lui în acele
aplicaţii .

6.13.3. Realizarea programului prin folosirea proiectelor

Metoda prezentată anterior are câteva dezavantaje. În primul rând, dacă se fac
modificări într-una din funcţii, la recompilarea programului se vor recompila şi celelalte (în
mod inutil ). Acest lucru are ca efect (mai ales pentru programe mari) un consum de timp
mare. În al doilea rând, dacă se doreşte reutilizarea codului, fără a oferi şi codul sursă, prima
variantă nu permite acest lucru. De asemenea, utilizarea bibliotecilor obj sau lib, nu este
permisă în aceea variantă. Aceste dezavantaje dispar dacă programul este format din mai multe
fişiere sursă, legate între ele prin intermediul proiectului. Această metodă a devenit astăzi o
metodă standard de lucru. Cea mai mare parte a mediile de programare, inclusiv cele vizuale
de ultimă generaţie, folosesc noţiunea de proiect pentru a pune la un loc entităţile programului.
Ce este de fapt un proiect C? Proiectul este de fapt un fişier cu extensia PRJ, fişier ce
conţine informaţii despre părţile programului. Acest proiect se construieşte şi gestionează prin
intermediul comenzilor din meniul Project al mediului de programare (toate variantele
Borland conţin acest meniu).
243 Limbajul de programare C
Vom prezenta mai departe modul de lucru cu proiecte. Realizarea unui proiect,
presupune parcurgerea mai multor etape:
• scrierea funcţiilor C în mai multe fişiere sursă, de obicei grupate după funcţiuni şi
fără a depăşi un număr de aproximativ 15 funcţii.
• realizarea fişierelor header, fişiere cu extensia h, ce conţin definiţiile de tipuri,
declaraţiile de variabile şi prototipurile funcţiilor dintr-un fişier sursă. Pentru
fiecare fişier sursă ( mai puţin cel principal) se va realiza un fişier header. Acest
fişier header se va include în alte fişiere sursă ce folosesc funcţii care nu sunt
definite acolo (folosind directiva #include). De exemplu, în fişierul principal va
trebui să includem fişierul header, dacă vom folosi funcţii de operare cu vectori.
Acest lucru este necesar compilatorului, acesta fiind avertizat despre existenţa
funcţiilor ( prin urmare, nu se va mai semnala o eroare legată de neexistenţa acelor
funcţii).
• crearea proiectului, folosind din meniul Compile, comanda Open, urmată de
numele proiectului. Atenţie, numele proiectului va deveni automat şi numele
aplicaţiei. După crearea proiectului, se adaugă fişierele sursă în fereastra Project,
ce va apărea în partea de jos a ecranului. Adăugarea o puteţi face folosind tasta
Insert. De asemenea, dacă aţi inserat greşit o sursă (atenţie nu fişierele header se
includ), o puteţi şterge folosind tasta Delete ( după ce v-aţi poziţionat pe fişierul
în cauză). Puteţi insera surse scrise în limbaj de asamblare sau bilioteci obj sau lib.

• se trece la compilarea şi execuţia aplicaţiei finale, folosind comenzile din meniul


Compile şi meniul Run ( Compile, Run , Make, Link ).
• închiderea proiectului, folosind din meniul Project , comanda Close. Atenţie,
proiectul este prioritar oricărui fişier sursă, aşa că dacă uitaţi să-l închideţi şi treceţi
la un alt program se va executa tot proiectul.
Vom exemplifica mai departe lucrul cu proiecte, folosind exemplu anterior referitor la
operarea cu structura de tip vector. Soluţia cu proiecte va presupune crearea unui proiect care
să includă două fişiere sursă. Prin urmare, pentru rezolvarea problemei va trebui să se creeze
un project în care să se includă două fişiere: cel cu codul funcţiilor şi cel cu funcţia main,
unde se apelează funcţiile definite anterior. De asemenea, va fi nevoie să se creeze un fişier cu
extensia h conţinând prototipurile funcţiilor din sursă. Le prezentăm mai departe.
1. Fişierul sursă, fişier ce conţine codul funcţiilor, funcţii.cpp :
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
void Citire(int X[],int &N)
{ int i;
printf("\nDati numarul de elemente ale vectorului:");
scanf("%d",&N);
Limbajul de programare C 244
for (i=0;i<N;i++)
{ printf("X[%d]=",i);
scanf("%d",&X[i]); }
}
void Afisare(int X[],int N)
{ int i;
printf("\nVectorul ");
for(i=0;i<N;i++)
printf("\n%d",X[i]); }
void Ordonare(int X[],int N)
{ int i,k,temp;
do
{ k=0;
for(i=0;i<N-1;i++)
if(X[i]>X[i+1])
{ temp=X[i];
X[i]=X[i+1];
X[i+1]=temp;
k=1; }
}

while (k!=0);
}
long Suma(int X[],int N)
{ int i;
long S;
for(S=0,i=0;i<N;i++)
S+=X[i];
return S; }
int Minim(int X[],int N)
{ int i;
int Min;
Min=X[0];
for(i=0;i<N;i++)
if (Min>X[i])
Min=X[i];
return Min; }
int NumararePoz(int X[],int N)
{ int i;
245 Limbajul de programare C
int Nr;
Nr=0;
for(i=0;i<N;i++)
if (X[i]>0)
Nr++;
return Nr; }
int Cautare(int X[],int N,int Y) //intoarce pozitia sau -1 in ca ca nu gaseste
{ int i;
for(i=0;i<N;i++)
if (X[i]==Y)
return 1;
return -1; }
2. Fişierul cu extensia h, numit functii.h, va fi :
void Citire(int X[],int &N);
void Afisare(int X[],int N);
void Ordonare(int X[],int N);
long Suma(int X[],int N);
int Minim(int X[],int N);
int NumararePoz(int X[],int N);
int Cautare(int X[],int N,int Y);

3. Al doilea fişier sursă, va fi fişierul principal ce foloseşte funcţiile prin intermediul


proiectului, să-i spunem princip.cpp:
#include <stdio.h>
#include <conio.h>
#include "functii.h"
void main()
{ int Optiune,Y;
int X[100],N;
do
{ clrscr();
printf("\n 1-Citire");
printf("\n 2-Afisare");
printf("\n 3-Calcul suma");
printf("\n 4-Ordonare");
printf("\n 5-Minim elemente");
printf("\n 6-Numarare elemente pozitive");
printf("\n 7-Cautare pozitie element");
printf("\n 8-Iesire");
Limbajul de programare C 246
printf("\nAlegeti optiunea ");
scanf("%d",&Optiune);
switch(Optiune)
{ case 1 :Citire(X,N);break;
case 2 :Afisare(X,N);break;
case 3 :printf("\nSuma elementelor este %ld",Suma(X,N));
break;
case 4 :Ordonare(X,N);break;
case 5 :printf("\nMinimul elementelor este %d",Minim(X,N));
break;
case 6 :printf("\nNumarul elementelor pozitive este %d",
NumararePoz(X,N));break;
case 7 :printf("\Dati elementul pe care il cautati");
scanf("%d",&Y);
printf("\nPozitia elementului este
%d",Cautare(X,N,Y));break;
}
getch();
}
while ( Optiune!=8);
}

În final, se va crea proiectul C folosind comanda Open din meniul Project şi se vor
include doar cele două fişiere sursă.

6.13.4. Etapele de realizare a unei aplicaţii

În general, orice problemă care se cere rezolvată cu ajutorul calculatorului implică


parcurgerea câtorva etape până la obţinerea unei aplicaţii funcţionale. Aceste etape sunt:
• Analiza problemei – se stabilesc datele de intrare, ieşire şi se formulează cerinţele
utilizatorilor în detaliu.
• Modelarea aplicaţiei – descompunerea aplicaţiei pe module, pe subprobleme.
• Analiza fiecărei subprobleme – determinarea metodelor de rezolvare pentru fiecare
subproblemă.
• Transpunerea algoritmilor – folosind un limbaj de programare se traduce
pseudocodul în limbajul de programare ales.
• Testarea programului – se testează rutinele aplicaţiei, în diverse situaţii şi se
elimină eventualele bug-uri.
• Elaborarea documentaţiei utilizator – se alcătuieşte un fel de help al aplicaţiei, un
manual de utilizare a aplicaţiei.
247 Limbajul de programare C
• Verificarea produsului final – constă în verificarea prodususlui final, în totalitatea
sa.
Vom prezenta aceste etape pe o aplicaţie ce permite evidenţa candidaţilor la un examen
de admitere.
1. Analiza problemei
Programatorul, în funcţie de sistemul de admitere al acelei instituţii ( probabil discută
cu responsabilii acelei instituţii sau pe baza unui caiet de sarcini), decide structura datelor,
decide ce structuri de date va folosi pentru modelarea problemei.
În primul rând, trebuie să lămurească ce date vor trebui introduse şi ce rezultate se vor
afişa. De asemenea, unde se vor păstra aceste date, care vor fi structurile de date folosite la
memorarea lor, în funcţie şi de limbajul folosit.
Pentru aplicaţia noastră, realizată în limbajul C, vom folosi fişiere binare pentru a
păstra datele candidaţilor. În principal vom avea un fişier Candidat.dat pentru a păstra datele
candidaţilor (inclusiv notele). De asemenea, se vor folosi alte două fişiere de date, numite
admisi.dat şi respinşi.dat care să păstreze candidaţii admişi, respectiv, pe cei respinşi. Fişierul
principal candidat.dat va conţine, pentru fiecare candidat, numele, prenumele, iniţiala tatălui,
notele la cele două probe de concurs şi media. Pentru rezultatele care trebuie să apară la
imprimantă se vor folosi fişiere text.
2. Modelarea aplicaţiei.
Problema admiterii este împărţită în mai multe module (subprobleme) după cum
urmează :

• Introducerea datelor personale ale candidaţilor, pe baza datelor de la inscrierea


candidaţilor. Pentru asta vom folosi modul Adaugare.
• Împărţirea candidaţilor pe săli, alfabetic, câte 20 în săli. Pentru asta vom folosi
modulul AfişareSăli.
• Introducerea notelor şi calculul mediei finale, după ce probelele de examen s-au
desfăşurat. Modulul care corespunde acestei operaţii este IntroducereNote.
• Determinarea candidaţilor admişi - conform criteriului de admitere al instituţiei
trebuie obţinută lista cu candidaţii admişi.
• Determinarea candidaţilor respinşi - conform criteriului de admitere al instituţiei
trebuie obţinută lista cu candidaţii respinşi.

3-4. Analiza fiecărei subprobleme şi transpunerea în limbajul de programare a


fiecărei subprobleme.

Pentru fiecare subproblemă se construieşte un subprogram corespunzător, subprogram


care să permită realizarea acelor cerinţe. Pentru exemplul nostru, vor fi funcţii care lucrează cu
fişiere. Programul principal va conţine un meniu text, ce permite selectarea uneia din opţiunile
de lucru:
Limbajul de programare C 248
• Funcţia Adăugare - adaugă candidaţii în fişierul principal candidat.dat. Ea poate fi
apelată de oricâte ori este nevoie ( ea adaugă la sfârşitul fişierului de date )
• Funcţia AfisareSali - ordonează datele din fişierul de date alfabetic şi crează un
fişier text cu candidaţii împărţiţi câte 20 într-o sală, fişier text ce poate fi apoi
tipărit la imprimantă.
• Funcţia Introducere Note – permite să se introducă notele pentru fiecare candidat
şi să se calculeze media la fiecare, note şi medie care sunt apoi depuse în fişierul
candidat.dat.
• Funcţiile Admisi şi Respinsi- construiesc alte două fişiere, fiecare conţinând
candidaţii admişi, respectiv respinşi. Primul conţine candidaţii care au obţinut cel
puţin 5 la ambele probe de examen, ordonaţi descrescător după medie. Al doilea,
cel cu candidaţii respinşi, conţine candidaţii care nu au obţinut cel puţin 5 la fiecare
probă, ordonaţi alfabetic. pentru a tipări aceste date la imprimantă, se crează două
fişiere text, care apoi se pot tipări sau se pot vizualiza.
Urmează apoi testarea fiecărui modul (a fiecărei proceduri) şi în final, testarea
programului final. De asemenea, se va realiza un manual de utilizare.
Programul C corespunzător este prezentat mai jos:
# include <conio.h>
# include <stdio.h>
# include <string.h>
# include <alloc.h>
# include <stdlib.h>

typedef struct Cand


{ char Nume[20],Prenume[20];
char Init[3];
char Dn[11] ;
int Ob1,Ob2;
float MedieG; } Candidat;
int Optiune;
void Adaugare(void)
{ FILE *F;
Candidat *C;
char Rasp[2];
F = fopen("candidat.dat","r+b");
if (F==NULL)
F = fopen("candidat.dat","wb");
fseek(F,0,2);
C = (Candidat *)malloc(sizeof(Candidat));
249 Limbajul de programare C
do
{ printf("\n Nume :");
scanf("%s", C->Nume);
printf("\n Initiala tatalui :");
scanf("%s", C->Init);
printf("\n Prenume :");
scanf("%s", C->Prenume);
printf("\n Data nasterii :");
scanf("%s", C->Dn);
fwrite(C,sizeof(Candidat),1,F);
printf("\n Mai aveti candidati de adaugat :");
scanf("%s",Rasp); }
while(stricmp(Rasp,"NU")!=0);
fclose(F);
}
void AfisareSali(void)
{ FILE *F;
FILE *F1;
Candidat *C,*C1;
unsigned K;
int I,Nr;
F=fopen("Candidat.dat","r+b");

F1=fopen("Sali.txt", "w");
//Fisierul text ce va contine lista cu candidatii pe sali
//Ordonam alfabetic candidatii pentru a-i imparti pe sali,
//ordonand fizic fisierul
C = (Candidat *)malloc(sizeof(Candidat));
C1 = (Candidat *)malloc(sizeof(Candidat));
do
{ K=0;
fseek(F,0,0);
while (! feof(F))
{
fread(C,sizeof(Candidat),1,F);
if (feof(F)) break;
fread(C1,sizeof(Candidat),1,F);
if (feof(F)) break;
Limbajul de programare C 250
if ((strcmp(C->Nume,C1->Nume)>0)||((strcmp(C->Nume,C1-
>Nume)==0)&&
(strcmp(C->Prenume,C1->Prenume)>0)))
{ fseek(F,-2*(long)sizeof(Candidat),1);
fwrite(C1,sizeof(Candidat),1,F);
fwrite(C,sizeof(Candidat),1,F);
K=1; }
fseek(F,-(long)sizeof(Candidat),1);
}
}
while (K);
I=1;
Nr=1;
fseek(F,0,0);
fprintf(F1,"\nSala numarul %d",Nr);
while (! feof(F))
{ fread(C,sizeof(Candidat),1,F);
if (feof(F)) break;
fprintf(F1,"\n%3d%s %s%s",I,C->Nume,C->Init,C->Prenume);
I++;
if (I==21)
{ Nr++;
fprintf(F1,"\nSala numarul :%d",Nr);
I=1; }
}

fclose(F);
fclose(F1);
}
void ModificareDate(void)
{ FILE *F;
Candidat *C;
char Nm[20],Pn[20];
printf("\nDati numele candidatului pentru care se modifica datele:");
scanf("%s",Nm);
printf("\nDati prenumele candidatului pentru care se modifica datele:");
scanf("%s",Pn);
F=fopen("Candidat.dat","r+b");
C=(Candidat *)malloc(sizeof(Candidat));
while (! feof(F))
251 Limbajul de programare C
{ fread(C,sizeof(Candidat),1,F);
if ((strcmp(Nm,C->Nume)==0) && (strcmp(C->Prenume,Pn)==0))
{ printf("\nObiect1 : ");scanf("%d",&C->Ob1);
printf("\nObiect2 : ");scanf("%d",&C->Ob2);
C->MedieG=(C->Ob1+C->Ob2) /2;
fseek(F,-(long)sizeof(Candidat),1);
fwrite(C,sizeof(Candidat),1,F);break; }
}
fclose(F);
}
void IntroducereNote(void)
{ FILE *F;
Candidat *C;
char Nm[20],Pn[20];
int Poz;
F=fopen("Candidat.dat","r+b");
C=(Candidat *)malloc(sizeof(Candidat));
Poz=0;
while (! feof(F))
{ fread(C,sizeof(Candidat),1,F);
if (feof(F))
break;
printf("\nCandidatul %s %s %s",C->Nume,C->Init,C->Prenume);
printf("\nNota la obiectul 1 : ");scanf("%d",&C->Ob1);

printf("\nNota la obiectul 2 : ");scanf("%d",&C->Ob2);


C->MedieG=(C->Ob1+C->Ob2) /2.0;
fseek(F,-(long)sizeof(Candidat),1);
fwrite(C,sizeof(Candidat),1,F);
Poz++;
fseek(F,(long)Poz*sizeof(Candidat),0);
}
fclose(F);
}
void AfisareAdmisi(void)
{ FILE *F,*G;
FILE *G1;
Candidat *C,*C1;
unsigned K;
Limbajul de programare C 252
unsigned int I,Nr;
F=fopen("Candidat.dat","rb");
G=fopen("Candidat.dat","wb"); //Fisierul contine lista cu admisii
C=(Candidat *)malloc(sizeof(Candidat));
C1=(Candidat *)malloc(sizeof(Candidat));
//Depunem candidatii admisi in fisierul Admisi.dat
I=0;
while (! feof(F))
{ fread(C,sizeof(Candidat),1,F);
if ((C->Ob1>=5) && (C->Ob2>=5))
{ write(C,sizeof(Candidat),1,F);
I++; }
}
fclose(F);
fclose(G);
if (I>0) //Se verifica daca au fost candidati admisi
{ G=fopen("Candidat.dat","r+b");
if (I>1) //Daca este un singur candidat, nu mai trebuie ordonat
{
/*Ordonam descrescator candidatii admisi (adica au peste 5 la fiecare obiect
ordonand fizic fisierul */
do
{ K=0;
fseek(G,0,0);

while (! feof(G))
{ fread(C,sizeof(Candidat),1,G);
if (feof(G)) break;
fread(C1,sizeof(Candidat),1,G);
if (feof(G)) break;
if ((C->MedieG < C1->MedieG)||((C->MedieG==C1->MedieG)&&((strcmp(C-Nume,
C1->Nume)>0) || ((strcmp(C->Nume,C1->Nume)==0)&&
(strcmp(C->Prenume,C1->Prenume)>0)))))
{ fseek(G,-2*(long)sizeof(Candidat),1);
fwrite(C1,sizeof(Candidat),1,G);
fwrite(C,sizeof(Candidat),1,G);
K=1; }
fseek(G,-(long)sizeof(Candidat),1);
}
253 Limbajul de programare C
}
while (K);
}
G1=fopen("Admisi.txt","w");
/*Fisierul text ce va contine lista cu candidatii
admisi, fisier ce se poate tipari la imprimanta */
Nr=1;
printf("\nCandidati admisi");
while (! feof(G))
{ fread(C,sizeof(Candidat),1,G);
fprintf(G1,"\n%3d %s %s %s %3d %3d %5.2f",Nr,C->Nume,C->Init,C->Prenume,
C->Ob1,C->Ob2,C->MedieG);
Nr++;
if (Nr % 61==0)
//Pe o pagina se pot scrie aproximativ 60 de randuri
{ printf(""); //Cod pentru a cere imprimantei sa scoata hartia
printf("Candidatii admisi :"); }
}
fclose(G);
fclose(G1);
}
else
printf("Nu avem candidati admisi");
}

void AfisareRespinsi(void)
{ FILE *F,*G;
FILE *G1;
Candidat *C,*C1;
unsigned K;
unsigned int I,Nr;
F=fopen("Candidat.dat","rb");
G=fopen("Candidat.dat","wb"); //Fisierul va contine lista cu respinsii
C=(Candidat *)malloc(sizeof(Candidat));
C1=(Candidat *)malloc(sizeof(Candidat));
//Depunem candidatii respinsi in fisierul respinsi.dat
I=0;
while (! feof(F))
{ fread(C,sizeof(Candidat),1,F);
if ((C->Ob1<5) || (C->Ob2<5))
Limbajul de programare C 254
{ write(C,sizeof(Candidat),1,G);
I++; }
}
fclose(F);
fclose(G);
if (I>0) //Se verifica daca au fost candidati respinsi
{ //Ordonam alfabetic candidatii respinsi, ordonand fizic fisierul
G=fopen("Candidat.dat","r+b");
if (I>1) //Daca este un singur candidat, nu mai trebuie ordonat
{
do
{ K=0;
fseek(G,0,0);
while (! feof(G))
{ fread(C,sizeof(Candidat),1,G);
if (feof(G)) break;
fread(C1,sizeof(Candidat),1,G);
if (feof(G)) break;
if ((strcmp(C->Nume,C1->Nume)>0) || ((strcmp(C->Nume,C1-Nume)==0)&&
(strcmp(C->Prenume,C1->Prenume)>0)))
{ fseek(G,-2*(long)sizeof(Candidat),1);
fwrite(C1,sizeof(Candidat),1,G);
fwrite(C,sizeof(Candidat),1,G);
K=1; }

fseek(G,-(long)sizeof(Candidat),1);
}
}
while (K);
G1=fopen("Respinsi.txt","w");
/*Fisierul text ce va contine lista cu candidatii
respinsi, fisier ce se poate tipari la imprimanta }*/
Nr=1;
printf("\nCandidati respinsi");
while (! feof(G))
{ fread(C,sizeof(Candidat),1,G);
fprintf(G1,"\n%3d %s %s %s %3d %3d %5.2f",Nr,C->Nume,C->Init,C->Prenume,
C->Ob1,C->Ob2,C->MedieG);
Nr++;
if (Nr % 61==0)
255 Limbajul de programare C
//Pe o pagina se pot scrie aproximativ 60 de randuri
{ printf("");
//Cod pentru a cere imprimantei sa scoata hartia
printf("Candidatii respinsi :"); }
}
fclose(G);
fclose(G1);
}
else
printf("\nNu avem candidati respinsi");
}
}
void main()
{ do
{ //clrscr();
printf("\nAlegeti optiunea:");
printf("\n1-Adaugare candidati");
printf("\n2-Afisare sali");
printf("\n3-Introducere note-calcul medie");
printf("\n4-Modificare note gresite-recalcularea mediei");
printf("\n5-Admisi");
printf("\n6-Respinsi");
printf("\n7-Iesire din program");

scanf("%d",&Optiune);
switch (Optiune)
{ case 1 : Adaugare();break;
case 2 : AfisareSali();break;
case 3 : IntroducereNote();break;
case 4 : ModificareDate();break;
case 5 : AfisareAdmisi();break;
case 6 : AfisareRespinsi();break; }
}
while (Optiune!=7);
}
Aplicaţia a fost realizată folosind un singur fişier sursă. Vom rescrie aplicaţia
folosind un proiect. Pentru aceasta vom împărţi sursa în două fişiere :
1. fişierul admitere.cpp, ce va conţine programul principal, prezentat în continuare:
#include <stdio.h>
Limbajul de programare C 256
#include <conio.h>
#include "functii.h"
void main()
{ int Optiune;
do
{ //clrscr();
printf("\nAlegeti optiunea:");
printf("\n1-Adaugare candidati");
printf("\n2-Afisare sali");
printf("\n3-Introducere note-calcul medie");
printf("\n4-Modificare note gresite-recalcularea mediei");
printf("\n5-Admisi");
printf("\n6-Respinsi");
printf("\n7-Iesire din program");
scanf("%d",&Optiune);
switch (Optiune)
{ case 1 : Adaugare();break;
case 2 : AfisareSali();break;
case 3 : IntroducereNote();break;
case 4 : ModificareDate();break;
case 5 : AfisareAdmisi();break;
case 6 : AfisareRespinsi();break; }
}
while (Optiune!=7);
}

2. fişierul funcţii.cpp, conţinând funcţiile de prelucrare a datelor despre candidaţi.


# include <conio.h>
# include <stdio.h>
# include <string.h>
# include <alloc.h>
# include <stdlib.h>
#include "functii.h"
void Adaugare(void)
{FILE *F;
Candidat *C;
char Rasp[2];
F = fopen("candidat.dat","r+b");
if (F==NULL)
F = fopen("candidat.dat","wb");
fseek(F,0,2);
257 Limbajul de programare C
C = (Candidat *)malloc(sizeof(Candidat));
do
{ printf("\n Nume :");
scanf("%s", C->Nume);
printf("\n Initiala tatalui :");
scanf("%s", C->Init);
printf("\n Prenume :");
scanf("%s", C->Prenume);
printf("\n Data nasterii :");
scanf("%s", C->Dn);
fwrite(C,sizeof(Candidat),1,F);
printf("\n Mai aveti candidati de adaugat :");
scanf("%s",Rasp); }
while(stricmp(Rasp,"NU")!=0);
fclose(F);
}
void AfisareSali(void)
{ FILE *F;
FILE *F1;
Candidat *C,*C1;
unsigned K;
int I,Nr;
F=fopen("Candidat.dat","r+b");
F1=fopen("Sali.txt", "w");

//Fisierul text ce va contine lista cu candidatii pe sali


//Ordonam alfabetic candidatii pentru a-i imparti pe sali,
//ordonand fizic fisierul
C = (Candidat *)malloc(sizeof(Candidat));
C1 = (Candidat *)malloc(sizeof(Candidat));
do
{ K=0;
fseek(F,0,0);
while (! feof(F))
{ fread(C,sizeof(Candidat),1,F);
if (feof(F)) break;
fread(C1,sizeof(Candidat),1,F);
if (feof(F)) break;
if ((strcmp(C->Nume,C1->Nume)>0)||((strcmp(C->Nume,C1->Nume)==0)&&
(strcmp(C->Prenume,C1->Prenume)>0)))
Limbajul de programare C 258
{ fseek(F,-2*(long)sizeof(Candidat),1);
fwrite(C1,sizeof(Candidat),1,F);
fwrite(C,sizeof(Candidat),1,F);
K=1; }
fseek(F,-(long)sizeof(Candidat),1);
}
}
while (K);
I=1;
Nr=1;
fseek(F,0,0);
fprintf(F1,"\nSala numarul %d",Nr);
while (! feof(F))
{ fread(C,sizeof(Candidat),1,F);
if (feof(F)) break;
fprintf(F1,"\n%3d%s %s%s",I,C->Nume,C->Init,C->Prenume);
I++;
if (I==21)
{ Nr++;
fprintf(F1,"\nSala numarul :%d",Nr);
I=1; }
}
fclose(F);
fclose(F1);
}

void ModificareDate(void)
{ FILE *F;
Candidat *C;
char Nm[20],Pn[20];
printf("\nDati numele candidatului pentru care se modifica datele:");
scanf("%s",Nm);
printf("\nDati prenumele candidatului pentru care se modifica datele:");
scanf("%s",Pn);
F=fopen("Candidat.dat","r+b");
C=(Candidat *)malloc(sizeof(Candidat));
while (! feof(F))
{ fread(C,sizeof(Candidat),1,F);
if ((strcmp(Nm,C->Nume)==0)&&(strcmp(C->Prenume,Pn)==0))
{ printf("\nObiect1 : ");scanf("%d",&C->Ob1);
259 Limbajul de programare C
printf("\nObiect2 : ");scanf("%d",&C->Ob2);
C->MedieG=(C->Ob1+C->Ob2) /2;
fseek(F,-(long)sizeof(Candidat),1);
fwrite(C,sizeof(Candidat),1,F);break;
}
}
fclose(F);
}
void IntroducereNote(void)
{ FILE *F;
Candidat *C;
char Nm[20],Pn[20];
int Poz;
F=fopen("Candidat.dat","r+b");
C=(Candidat *)malloc(sizeof(Candidat));
Poz=0;
while (! feof(F))
{ fread(C,sizeof(Candidat),1,F);
if (feof(F))break;
printf("\nCandidatul %s %s %s",C->Nume,C->Init,C->Prenume);
printf("\nNota la obiectul 1 : ");scanf("%d",&C->Ob1);
printf("\nNota la obiectul 2 : ");scanf("%d",&C->Ob2);
C->MedieG=(C->Ob1+C->Ob2) /2.0;
fseek(F,-(long)sizeof(Candidat),1);
fwrite(C,sizeof(Candidat),1,F);

Poz++;
fseek(F,(long)Poz*sizeof(Candidat),0);
}
fclose(F);
}
void AfisareAdmisi(void)
{ FILE *F,*G;
FILE *G1;
Candidat *C,*C1;
unsigned K;
unsigned int I,Nr;
F=fopen("Candidat.dat","rb");
G=fopen("Candidat.dat","wb"); //Fisierul va contine lista cu admisi
Limbajul de programare C 260
C=(Candidat *)malloc(sizeof(Candidat));
C1=(Candidat *)malloc(sizeof(Candidat));
//Depunem candidatii admisi in fisierul Admisi.dat
I=0;
while (! feof(F))
{ fread(C,sizeof(Candidat),1,F);
if ((C->Ob1>=5) && (C->Ob2>=5))
{ fwrite(C,sizeof(Candidat),1,F);
I++; }
}
fclose(F);
fclose(G);
if (I>0) //Se verifica daca au fost candidati
admisi
{ G=fopen("Candidat.dat","r+b");
if (I>1) //Daca este un singur candidat , nu mai trebuie ordonat
{
/*Ordonam descrescator candidatii admisi (adica au peste 5 la fiecare obiect
ordonand fizic fisierul */
do
{ K=0;
fseek(G,0,0);
while (! feof(G))
{ fread(C,sizeof(Candidat),1,G);
if (feof(G)) break;
fread(C1,sizeof(Candidat),1,G);

if (feof(G)) break;
if ((C->MedieG < C1->MedieG)||((C->MedieG==C1->MedieG)&&((strcmp(C->
Nume ,C1->Nume)>0) || ((strcmp(C->Nume,C1->Nume)==0)&&(strcmp(C->Prenume
,C1->Prenume)>0)))))
{ fseek(G,-
2*(long)sizeof(Candidat),1);

fwrite(C1,sizeof(Candidat),1,G);

fwrite(C,sizeof(Candidat),1,G);
K=1;
}
261 Limbajul de programare C
fseek(G,-(long)sizeof(Candidat),1);
}
}
while (K);
}
G1=fopen("Admisi.txt","w");
/*Fisierul text ce va contine lista cu candidatii
admisi, fisier ce se poate tipari la imprimanta */
Nr=1;
printf("\nCandidati admisi");
while (! feof(G))
{ fread(C,sizeof(Candidat),1,G);
fprintf(G1,"\n%3d %s %s %s %3d %3d %5.2f" ,Nr,C->Nume,C->Init,C->Prenume,C->Ob1
,C->Ob2,C->MedieG);
Nr++;
if (Nr % 61==0)
//Pe o pagina se pot scrie aproximativ 60 de randuri
{ printf("");
//Cod pentru a cere imprimantei sa scoata hartia
printf("Candidatii admisi :"); }
}
fclose(G);
fclose(G1);
}
else
printf("Nu avem candidati admisi");
}
void AfisareRespinsi(void)
{ FILE *F,*G;
FILE *G1;

Candidat *C,*C1;
unsigned K;
unsigned int I,Nr;
F=fopen("Candidat.dat","rb");
G=fopen("Candidat.dat","wb"); //Fisierul va contine lista cu respinsi
C=(Candidat *)malloc(sizeof(Candidat));
C1=(Candidat *)malloc(sizeof(Candidat));
//Depunem candidatii respinsi in fisierul respinsi.dat
Limbajul de programare C 262
I=0;
while (! feof(F))
{ fread(C,sizeof(Candidat),1,F);
if ((C->Ob1<5) || (C->Ob2<5))
{ fwrite(C,sizeof(Candidat),1,G);
I++; }
}
fclose(F);
fclose(G);
if (I>0) //Se verifica daca au fost candidati
respinsi
{
//Ordonam alfabetic candidatii respinsi, ordonand fizic fisierul
G=fopen("Candidat.dat","r+b");
if (I>1) //Daca este un singur candidat , nu mai trebuie ordonat
{
do
{ K=0;
fseek(G,0,0);
while (! feof(G))
{ read(C,sizeof(Candidat),1,G);
if (feof(G)) break;
fread(C1,sizeof(Candidat),1,G);
if (feof(G)) break;
if ((strcmp(C->Nume,C1->Nume)>0) || ((strcmp(C->Nume,C1->Nume)==0)&&
(strcmp(C->Prenume,C1->Prenume)>0)))
{ fseek(G,-
2*(long)sizeof(Candidat),1);
fwrite(C1,sizeof(Candidat),1,G);
fwrite(C,sizeof(Candidat),1,G);
K=1; }
fseek(G,-(long)sizeof(Candidat),1);
}
}

while (K);
G1=fopen("Respinsi.txt","w");
/*Fisierul text ce va contine lista cu candidatii
respinsi, fisier ce se poate tipari la imprimanta }*/
Nr=1;
263 Limbajul de programare C
printf("\nCandidati respinsi");
while (! feof(G))
{ fread(C,sizeof(Candidat),1,G);
fprintf(G1,"\n%3d %s %s %s %3d %3d %5.2f" ,Nr,C->Nume,C->Init,C->Prenume,C->Ob1,
C->Ob2,C->MedieG);
Nr++;
if (Nr % 61==0)
//Pe o pagina se pot scrie aproximativ 60 de randuri
{ printf("");
//Cod pentru a cere imprimantei sa scoata hartia
printf("Candidatii respinsi :"); }
}
fclose(G);
fclose(G1);
}
else
printf("\nNu avem candidati respinsi");
}
}
3. fişierul cu prototipurile funcţiilor şi definiţia tipului de date candidat, fişier numit
funcţii.h:
typedef struct Cand { char Nume[20],Prenume[20];
char Init[3];
char Dn[11] ;
int Ob1,Ob2;
float MedieG; } Candidat;
void Adaugare(void);
void AfisareSali(void);
void ModificareDate(void);
void IntroducereNote(void);
void AfisareAdmisi(void);
void AfisareRespinsi(void);

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