programare c

184
Limbajul de programare C 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. 134

Transcript of programare c

Page 1: programare c

Limbajul de programare C

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

134

Page 2: programare c

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 cheieLungime

în biţiTip de reprezentare internă

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,

135

Page 3: programare c

Limbajul de programare C are valoarea absolută în intervalul [3.4*10-38, 3.4*1038]. 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.

ReprezentareLungimea

reprezentăriiLungime de reprezentare internă

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

012345 16 biţiîntreg octal reprezentat în binar

(o cifră octală se reprezintă pe 3 biţi)

0xa12b 16 biţiîntreg hexazecimal reprezentat în binar

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

136

Page 4: programare c

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

CaracterReprezentare prin constantă

caracterValoare

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.

137

Page 5: programare c

Limbajul de programare C 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:

138

Page 6: programare c

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:

139

Page 7: programare c

Limbajul de programare C ! (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:

140

Page 8: programare c

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

y

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

~x

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

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

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

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

care are valoarea 0.

Aplicarea operatorului de deplasare la stânga, << , este echivalentă cu o înmulţire a lui

x cu 2y, iar a operatorului de deplasare la dreapta, >>, cu o împărţire a lui x cu 2y.

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=

141

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

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

1 1 1 1 1 1 1 1 1 1 1 1 10

0 0 1

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

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

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

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 1 1 0 0 1 0 1

C1 C2

Page 9: programare c

Limbajul de programare C

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

142

Page 10: programare c

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;

143

Page 11: programare c

Limbajul de programare C 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

144

Page 12: programare c

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

145

Page 13: programare c

Limbajul de programare C

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

(double)(i/j)

se realizează împărţirea întreagă

între i şi j şi rezultatul se

converteşte spre double.

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:

146

Page 14: programare c

Limbajul de programare C 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ă

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) >> << & ^ !

,

147

Page 15: programare c

Limbajul de programare C 148

Page 16: programare c

Limbajul de programare C

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ă

Fişierele sursă pot fi incluse cu ajutorul construcţiei #include. Această construcţie are

una din următoarele formate:

149

Page 17: programare c

Limbajul de programare C #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ă.

6.5.1. Funcţia standard printf

150

Page 18: programare c

Limbajul de programare C 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ă

ddata se converteşte din tipul int şi zecimal şi se scriu la ieşire caracterele zecimale

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;

xdata se converteşte din tipul int în hexazecimal şi se scriu la ieşire caracterele ei

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;

cvaloarea parametrului care îi corespunde se consideră că reprezintă codul ASCII al

unui caracter şi se scrie caracterul respectiv;

sparametrul care-i corespunde se scrie ca un şir de caractere; se consideră că şirul se

termină la întâlnirea caracterului NUL (‘\0’);

f

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ă

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;

e

conversia se realizează de tipul float sau double în formatul:

d.dd..deddd unde numărul cifrelor de după punctul zecimal este dat de precizia

specificatorului de format sau este egal cu 6, dacă acesta este absent; partea întreagă

este precedată de minus dacă numărul este negativ;

151

Page 19: programare c

Limbajul de programare C

E

ca şi în cazul literei e cu deosebirea că litera e se schimbă cu litera E:

d.dd..dEddd în ambele cazuri, la exponent se va scrie una, două sau trei cifre, în

funcţie de valoarea numărului;

g

se aplică una din conversiile definite de literele f sau e, alegându-se aceea care se

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.

152

Page 20: programare c

Limbajul de programare C 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. 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 */

153

Page 21: programare c

Limbajul de programare C { int ziua, luna, an;

char nume [MAX+1], prenume [MAX+1];

scanf (“%50s%50s%d%d%d”, nume, prenume, &zi, &luna, &an);

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.

154

Page 22: programare c

Limbajul de programare C

6.5.6. Funcţiile standard gets şi puts

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:

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

155

Page 23: programare c

Limbajul de programare 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ă.

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;

156

Page 24: programare c

Limbajul de programare 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:

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

Page 25: programare c

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;

158

Page 26: programare c

Limbajul de programare C while (exp2)

{

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>

main()

/* numara caracterele citite */

{

long n;

n = 0;

while (getchar() ! =EOF) n++

printf(“numarul caracterelor citite= %ld\n”, n);

}

#include <stdio.h>

main()

/* numara caracterele citite */

{

long n;

for (n = 0;getchar() ! =EOF) n++);

printf(“numarul caracterelor citite= %ld\n”, n);

}

159

Page 27: programare c

Limbajul de programare C 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.

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

160

Page 28: programare c

Limbajul de programare C }

6.6.6. Instrucţiunea DO-WHILE

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

161

Page 29: programare c

Limbajul de programare C } /* sfârşit else */

}

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;

162

Page 30: programare c

Limbajul de programare C case 10: printf (“Octombrie”); break;

case 11: printf (“Decembrie”); break;

case 12: printf (“Noiembrie”); break;

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

163

Page 31: programare c

Limbajul de programare C if (temperatura<0) continue;

nr_temp_pozitive=nr_temp_pozitive+1;

suma_temp=suma_temp+temperatura;

}

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.

164

Page 32: programare c

Limbajul de programare C

6.7. Declaraţia de tablou

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

Page 33: programare c

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:

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;

166

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

Fig. 6.1. Repartizarea memoriei pentru tabloul tab.

Page 34: programare c

Limbajul de programare C 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:

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:

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

printf("\nIntroduceti nr. de coloane:");

scanf("%d",&m);

167

Page 35: programare c

Limbajul de programare C 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

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

168

Page 36: programare c

Limbajul de programare C 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

Page 37: programare c

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

170

Page 38: programare c

Limbajul de programare C for (i=0;i<n;i++)

printf("%d ",a[i]);

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

171

Page 39: programare c

Limbajul de programare C 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.

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

172

Page 40: programare c

Limbajul de programare C 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.

Î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; }

173

Page 41: programare c

Limbajul de programare C int invers(int n)

{ int inv;

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ă

174

Page 42: programare c

Limbajul de programare C specială afectată programului. În funcţie de modul în care se alocă memoria, vom distinge mai

multe clase de memorie.

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;

………………

175

Page 43: programare c

Limbajul de programare C x = i*x;

…………… }

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

176

Page 44: programare c

Limbajul de programare C int i; /* definitia variabilei i */

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

……………… }

f2(…)…

{ extern int i; /* declaratie externa pentru i */

static double s; /* variabila statica */

177

Page 45: programare c

Limbajul de programare C ………………

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

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

178

Page 46: programare c

Limbajul de programare C 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.

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:

179

Page 47: programare c

Limbajul de programare C

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

Fie declaraţiile:

int x,y;

int p;

1. y = x+100; este echivalentă cu secvenţa:

p = &x;

180

x

Memoria p (adresa de memoriea variabilei x)

5

Memoria 2500

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

Page 48: programare c

Limbajul de programare C 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;

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

}

181

Page 49: programare c

Limbajul de programare C

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:

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

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;

182

Adresa lui a(0)

Adresa lui a(0)

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

a

p

p = a

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

Page 50: programare c

Limbajul de programare C 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.

Efect:

- expresia p+n măreşte valoarea lui p cu nt, 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 nt.

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

183

Page 51: programare c

Limbajul de programare C

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:

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.

184

Page 52: programare c

Limbajul de programare C 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.

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

185

Page 53: programare c

Limbajul de programare C 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>

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

186

Page 54: programare c

Limbajul de programare C 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);

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++)

187

Page 55: programare c

Limbajul de programare C 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].

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

188

Page 56: programare c

Limbajul de programare C 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.

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

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:

189

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

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

... ... ...

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

tab[0]

tab[1]

...

tab[m-1]

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

Page 57: programare c

Limbajul de programare C 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.

Instrucţiuni Elementul afişat Valoarea elementului

p = tab[0];

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

p = tab[1];

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

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];

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

190

Page 58: programare c

Limbajul de programare C 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]

Note[0]

Note[1]

….

Note[19]

Prezentăm programul sursă în varianta cu tablouri de pointeri:

#include <conio.h>

#include <stdio.h>

#include <alloc.h>

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++)

191

4 5 8 9 6 7 5

9 9 6 7 9 8 8

8 7 4 5 6 7 8

Fig. 6.5. Memorarea notelor studenţilor utilizând pointerii.

int *Note[20];

adică ca un tablou de 20 de pointeri, câte un

pointer pentru fiecare candidat. De fapt,

fiecare pointer va conţine adresa unei zone

de memorie unde sunt stocate notele acelui

candidat (zona ce trebuie rezervată ). Acest

lucru se observă în figura 6.5:

Page 59: programare c

Limbajul de programare C 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();

Afisare(Note, N, M);

for (I=0; I<N; I++) free(Note[I]); // se eliberează memoria rezervată

getch(); }

Un alt exemplu, în care tablourile de pointeri oferă o soluţie mai bună din punct de

vedere al spaţiului ocupat, este aceea în care se lucrează cu liste de denumiri de obiecte. Să

considerăm situaţia în care se păstrează numele a n persoane. Fiecare nume de persoană are o

anumită lungime. Prin urmare, cea mai bună soluţie constă în folosirea unui tablou de pointeri

î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]

192

POPESCU ION

AVRAMESCU DORU

ION ION

Fig. 6.6. Memorarea datelor personale utilizând pointerii.

Vom prezenta, mai departe,

un exemplu de cum se prelucrează o

astfel de listă de nume de persoane,

folosind un tablou de pointeri.

Pentru exemplificare, vom afişa

numele acestora în ordine

alfabetică, convertite în mari şi vom

afişa cel mai lung nume. Vom

declara lista cu numele celor n

persoane astfel:

Page 60: programare c

Limbajul de programare C

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;

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

193

Page 61: programare c

Limbajul de programare C 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

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

194

Page 62: programare c

Limbajul de programare C 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

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

195

Page 63: programare c

Limbajul de programare C 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);

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

196

Page 64: programare c

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]”);

197

Page 65: programare c

Limbajul de programare C }

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

else if(strlen(argv[10])>10)

198

Page 66: programare c

Limbajul de programare C 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.

Formatele declaraţiei cu modificatorul const sunt următoarele:

tip const nume = valoare;

199

Page 67: programare c

Limbajul de programare C 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;

*(int *) &k=3;

*(int *) &1 = -3;

200

Page 68: programare c

Limbajul de programare C 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

Valorile lui x, pi

x = 1.2345678900 pi=3.1415900000

Valorile modificate ale lui I si l

201

Page 69: programare c

Limbajul de programare C 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.

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:

202

Page 70: programare c

Limbajul de programare C 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.

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

203

Page 71: programare c

Limbajul de programare C 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.

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

204

Page 72: programare c

Limbajul de programare C 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; }

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:

205

Page 73: programare c

Limbajul de programare C 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:

- 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

206

Page 74: programare c

Limbajul de programare C 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.

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:

207

Page 75: programare c

Limbajul de programare C 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); }

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>

208

Page 76: programare c

Limbajul de programare C 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;

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ă

209

Page 77: programare c

Limbajul de programare C 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");

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

210

Page 78: programare c

Limbajul de programare C 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++)

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;

211

Page 79: programare c

Limbajul de programare C 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()

{ 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

212

Page 80: programare c

Limbajul de programare C 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:

unde:

a,b,c sunt laturile triunghiului;

, este semiperimetrul triunghiului,

condiţia de existenţă a triunghiului fiind: p>a, p>b şi p>c.

Programul este prezentat în continuare:

#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()

213

Page 81: programare c

Limbajul de programare C { 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;

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

214

Page 82: programare c

Limbajul de programare C 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

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;

215

Page 83: programare c

Limbajul de programare C 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.

Observaţii:

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;

216

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.

Page 84: programare c

Limbajul de programare C 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

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;

217

Page 85: programare c

Limbajul de programare C Î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>

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

218

Page 86: programare c

Limbajul de programare C 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");

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

219

Page 87: programare c

Limbajul de programare C 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);

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

220

Page 88: programare c

Limbajul de programare C 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;

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

221

Page 89: programare c

Limbajul de programare C 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_RDWO_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.

Î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;

...

222

Page 90: programare c

Limbajul de programare C 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;

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

223

Page 91: programare c

Limbajul de programare C 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)

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

224

Page 92: programare c

Limbajul de programare C î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.

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.

225

Page 93: programare c

Limbajul de programare C 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

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_IWRITES_IREAD)) == -1)

{ printf(“nu se poate deschide fisierul fis1.dat in creare \n”);

exit(1); }

if ((df2 = creat(“fis2.dat”, S_IWRITES_IREAD)) == -1)

226

Page 94: programare c

Limbajul de programare C { 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); }

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

227

Page 95: programare c

Limbajul de programare C 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

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.

228

Page 96: programare c

Limbajul de programare C 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:

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.

229

Page 97: programare c

Limbajul de programare C

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

}

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

230

Page 98: programare c

Limbajul de programare C 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.

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.

231

Page 99: programare c

Limbajul de programare C 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

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.

232

Page 100: programare c

Limbajul de programare C Î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

tip denumire um cod pret cantitate

I REZISTENTA 010KO 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”);

233

Page 101: programare c

Limbajul de programare C 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++) {

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];

234

Page 102: programare c

Limbajul de programare C 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); }

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

235

Page 103: programare c

Limbajul de programare C 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.

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>

236

Page 104: programare c

Limbajul de programare C // 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]);

}

}

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;

237

Page 105: programare c

Limbajul de programare C 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");

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>

238

Page 106: programare c

Limbajul de programare C 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]); }

}

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;

239

Page 107: programare c

Limbajul de programare C 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;

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

240

Page 108: programare c

Limbajul de programare C 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);

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

241

Page 109: programare c

Limbajul de programare C 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).

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.

242

Page 110: programare c

Limbajul de programare C 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);

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;

243

Page 111: programare c

Limbajul de programare C 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;

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"

244

Page 112: programare c

Limbajul de programare C 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);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

245

Page 113: programare c

Limbajul de programare C Î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.

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.

246

Page 114: programare c

Limbajul de programare C 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:

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] ;

247

Page 115: programare c

Limbajul de programare C 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));

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

248

Page 116: programare c

Limbajul de programare C 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)))

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

}

249

Page 117: programare c

Limbajul de programare C 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);

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

250

Page 118: programare c

Limbajul de programare C 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 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;

251

Page 119: programare c

Limbajul de programare C 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);

}

}

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;

252

Page 120: programare c

Limbajul de programare C 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))

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

253

Page 121: programare c

Limbajul de programare C 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)

//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;

254

Page 122: programare c

Limbajul de programare C 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>#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>

255

Page 123: programare c

Limbajul de programare C # 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);

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

256

Page 124: programare c

Limbajul de programare C 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)))

{ 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;

257

Page 125: programare c

Limbajul de programare 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);

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

}

258

Page 126: programare c

Limbajul de programare C 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

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;

259

Page 127: programare c

Limbajul de programare C 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);

}

}

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;

260

Page 128: programare c

Limbajul de programare C 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

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

} }

261

Page 129: programare c

Limbajul de programare C 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)

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

262