Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Cătălin Silvestru
Algoritmi
în programarea
calculatoarelor
Material didactic pentru ID
Editura ASE
Bucureşti
2010
Algoritmi în programare 2
Editura ASE
Piaţa Romană nr. 6, sector 1, Bucureşti, România
cod 010374
www.ase.ro
www.editura.ase.ro
editura@ase.ro
Referenţi:
ISBN 978-606-505-465-3
* Material didactic pentru ID * 3
Cuprins
2. Subprograme ...................................................................................................................... 16
Obiectivele unităţii de învăţare ............................................................................................. 16
2.1. Construirea şi apelul subprogramelor ..................................................................... 16
2.2. Transferul datelor între apelator şi apelat............................................................... 19
2.2.1. Transferul prin parametri................................................................................... 19
2.2.2. Simularea transmiterii parametrilor prin adresă ............................................. 21
2.2.3. Comunicaţia prin variabile globale .................................................................... 22
2.3. Pointeri spre funcţii.................................................................................................... 23
2.4. Funcţii cu număr variabil de parametri .................................................................. 27
2.5. Calcul recursiv............................................................................................................ 30
2.6. Exemple de aplicaţii cu subprograme recursive...................................................... 32
Răspunsuri şi comentarii la testele de autoevaluare ........................................................... 37
Rezumat................................................................................................................................... 39
Bibliografia unităţii de învăţare............................................................................................ 39
* Material didactic pentru ID * 5
Cuprins
După studierea acestei unităţi de învăţare, studenţii vor avea cunoştinţe teoretice şi
abilităţi practice despre tipurile dinamice de date utilizate în programarea calculatoa-
relor şi vor putea utiliza aceste tipuri de date şi structurile de tip masiv de date pen-
tru rezolvarea problemelor de programare. Concret, se vor asimila cunoştinţe şi abilităţi de lu-
cru privind:
tipurile de date pointer;
operaţii cu datele de tip pointer şi aritmetica pointerilor;
legătura dintre pointeri şi masivele de date, în limbajul C;
alocarea dinamică a datelor;
tratarea parametrilor primiţi în linia de comandă.
Pointerul este un tip de dată predefinit, care are ca valoare adresa unei zone de memo-
rie (figura 1.1).
Memoria internă
Segmen:offset
Exemplu
1. *&nume reprezintă valoarea de la adresa variabilei nume (valoarea variabilei nume).
Fie TIP un tip de dată oarecare în limbajul C (inclusiv void). Declararea TIP* nume;
este o declaraţie de pointer. TIP* este un nou tip de dată denumit pointer spre TIP, iar nume
este o variabilă de tipul pointer spre TIP.
Exemple
2. int* n; ⇒ n este o variabilă de tip pointer spre întreg;
3. struct complex {a,b:real;}* x; ⇒ x este o variabilă de tip pointer spre o structură de ti-
pul complex;
4. void* p; ⇒ p este o variabilă de tip pointer spre void; p poate primi ca valoare adresa
unei zone de memorie de orice tip.
Dacă TIP este un tip oarecare (mai puţin void) atunci tipul TIP* este adresa unei zone
de memorie de un tip cunoscut. Operaţiile care se pot efectua asupra zonei respective de me-
morie sînt definite de tipul acesteia. Dacă TIP este void, atunci TIP* este adresa unei zone de
memorie de tip necunoscut. Deoarece nu se cunoaşte tipul zonei de memorie, nu sînt definite
operaţiile care se pot efectua asupra ei.
Pentru pointerii din exemplele anterioare se rezervă în memoria principală (în segmen-
tul de date) cîte o zonă de 4B în care se va memora o adresă.
Cînd variabila nume nu este iniţializată prin declarare, ea primeşte implicit valoarea
NULL. La execuţie, poate primi ca valoare adresa unei variabile numai de tipul TIP. Dacă TIP
este void, atunci nume poate primi adresa oricărei variabile, de orice tip.
Algoritmi în programare 8
Exemple
4. int* nume; int a; float b;
nume = &a; ⇒ este o atribuire corectă; nume are ca valoare adresa variabilei a.
nume = &b; ⇒ este o atribuire incorectă; nume poate primi ca valoare doar adresa
unei variabile întregi.
5. void* nume; ⇒ pointer fără tip
int a; float b; ⇒ variabile de tip întreg, respectiv real
nume = &a; ⇒ atribuire corectă
nume = &b; ⇒ atribuire corectă; nume poate primi ca valoare adresa oricărei
variabile, de orice tip.
Se observă folosirea operatorului de referenţiere & pentru a crea o referinţă către vari-
abila a. La alocarea dinamică a memoriei se foloseşte o altă metodă pentru iniţializarea unui
pointer. Operatorul de dereferenţiere se utilizează atît pentru definirea tipului pointer, cît şi
pentru referirea datelor de la adresa indicată de pointer.
Exemplu
6.
int a,b,c; int* nume;
void* nume2;
b=5;
nume=&a;
*nume=b;
c=*nume+b;
nume2=&b;
*(int*)nume2=10;
c=*(int*)nume2;
Teste de autoevaluare
1. Care sînt operatorii specifici lucrului cu pointeri în limbajul C? Daţi exemple de
utilizare a lor.
2. Care sînt cele două tipuri de pointeri? Care sînt diferenţele dintre ele?
Incrementare/decrementare
Dacă nume este pointer spre un tip TIP, prin incrementare/decrementare, valoarea lui
nume se incrementează/decrementează cu numărul de octeţi necesari pentru a memora o dată
de tip TIP, adică cu sizeof(TIP).
nume++ Ù nume are ca valoare o adresă care este incrementată şi primeşte valoarea
nume+sizeof(int) (care este adresa lui b);
nume2-- Ù nume are ca valoare o adresă care este decrementată şi primeşte valoa-
rea nume-sizeof(int) (care este adresa lui c);
nume nume2 c a b
nume nume2 c a b
4B 4B 2B 2B 2B
4B 4B 2B 2B 2B
Analog se execută operaţiile ++nume şi --nume.
Exemplu
7.
float v[20];
float* p;
int i;
p=&v[i]; unde i poate avea valori între 0 şi 19
În urma atribuirii ++p sau p++, p va avea ca valoare adresa lui v[i] plus 4 octeţi, adică adresa
lui v[i+1].
Exemplu
8. Fie p şi q pointeri spre tipul float (float* p, *q). Presupunînd că p a fost iniţializat
cu valoarea 0x0fff:0x3450, în urma operaţiei q=p+3, q primeşte valoarea 0xfff:0x345c (se adu-
nă 3*4 octeţi). În urma operaţiei q=p-2, q primeşte valoarea 0xffff:0x344a (se scad 2*4 octeţi).
Exemplu
9.
float* p,q,r,t;
float a,b;
p=&a; q=&b; r=&a;
a=5; b=7;
if(t) printf("Pointer initializat!\n");
else printf("Pointer neinitializat!\n");
if(p==r) printf("Pointeri egali\n");
else printf("Pointeri diferiti\n");
if(p>q) printf("%d\n",a);
else printf("%d\n",b);
Pe ecran se va afişa:
Pointer neinitializat!
Pointeri egali
7
deoarece t are valoarea NULL, variabilele p şi r au ca valoare adresa lui a, iar q conţine adresa
lui b, care este mai mare decît a lui a (datorită faptului că a a fost alocat primul).
a b
Fig.1.2. Reprezentarea semnificaţiei variabilelor din exemplul anterior
Atenţie: vorbim despre diferenţa dintre doi pointeri (înţelegînd distanţa dintre cele
două adrese), nu despre scăderea a doi pointeri.
Atenţie: această figură NU reprezintă modul de alocare în memorie a unei matrice sta-
tice! Doar pentru matricele alocate dinamic zonele de memorie sînt alocate în acest
fel.
m
m[0,0] m[0,1] … m[0,49]
m[0,0] m[0,1] … m[0,49]
m[0]
m[1] m[2,0] m[2,1] … m[2,49]
m[2]
m[3]
m[3,0] m[3,1] … m[3,49]
m[4] m[4,0] m[4,1] … m[4,49]
…
… … … …
m[49]
m[49,0] m[49,1] … m[49,49
Fig.1.3. Reprezentarea modului de alocare dinamică a spaţiului necesar
pentru memorarea unei matrice 50x50
Pentru a lucra cu elementele unui masiv static se poate folosi adresarea indexată (m[i]
pentru vectori sau m[i][j] pentru matrice) sau adresarea elementelor prin pointeri (*(m+i)
pentru vectori sau *(*(m+i)+j) pentru matrice etc).
Mai mult, pentru vectori se poate declara un pointer iniţializat cu adresa de început a
masivului, iar elementele masivului să fie referite prin intermediul acestui pointer.
Exemplu
11. float* v[10]; float* p; p=v;
Algoritmi în programare 12
După atribuire, pointerul p conţine adresa de început a masivului şi poate fi folosit pentru re-
ferirea elementelor masivului. De exemplu, v[3] şi p[3] referă aceeaşi zonă de memorie.
Test de autoevaluare
3. Să se scrie secvenţa de program care citeşte de la tastatură elementele unei matrice,
folosind expresii cu pointeri pentru adresarea elementelor matricei.
Funcţia rezervă o zonă de n octeţi în heap şi returnează adresa acesteia. Deoarece funcţia re-
turnează pointer spre void este necesară conversia rezultatului spre tipul dorit, astfel:
int* nume;
nume=(int *) malloc(sizeof(int)); ⇔ rezervă în heap spaţiu pentru o valoare de tip întreg.
Eliberarea unei zone de memorie rezervate anterior se face prin funcţia standard:
Funcţia primeşte ca parametru un pointer (indiferent de tip) spre zona de memorie pe care tre-
buie să o elibereze.
Limbajul C oferă posibilitatea de a aloca contiguu zone de memorie pentru mai multe
date de acelaşi tip, prin funcţia standard:
Funcţia calloc rezervă o zonă contiguă de memorie pentru mai multe elemente de acelaşi tip,
întorcînd un pointer spre zona respectivă.
Există şi o variantă a lui malloc care returnează în mod explicit un pointer „îndepărtat”
(far):
void* farmalloc(unsigned long n);
Pentru eliberarea unei zone de memorie rezervate prin farmalloc se foloseşte funcţia
standard:
void farfree(void* p);
Exemple
12. int* masiv; masiv=(int*)calloc(50,sizeof(int)); ⇔ rezervă spaţiu de
memorie pentru un vector cu 50 de elemente întregi.
13. Alocarea de spaţiu în heap pentru o matrice se face conform figurii 1.3 pentru a putea ac-
cesa elementele la fel ca în cazul unei matrice statice, prin dublă indexare;
int** m;
int n,p;
/* se alocă spaţiu pentru vectorul cu adresele celor n linii ale matricei */
m=(int**)malloc(m*sizeof(int*));
for(int i=0;i<m;i++)
/*se alocă spaţiu pentru fiecare linie a matricei, cîte p elemente*/
m[i]=(int*)malloc(n*sizeof(int));
* Material didactic pentru ID * 13
14. Să se scrie o funcţie care să citească cel mult n numere întregi şi le păstreze în zona de
memorie a cărei adresă de început este dată printr-un pointer. Funcţia returnează numărul va-
lorilor citite.
int cit_nr(int n, int* p)
{ int nr, i;
int* q=p+n; // q este adresa unde se termina zona
// rezervata pentru cele n numere
i=0;
while(p<q) // cit timp nu s-au citit n numere
{ printf("Numarul %d= ", i);
if(scanf("%d", &nr)!=1) break; // in caz de eroare la citire
// se termina ciclul
*p=nr;
p++; i++;
}
return(i); }
Teste de autoevaluare
4. Să se scrie o secvenţă de program pentru alocarea dinamică spaţiului necesar pentru
un vector şi citirea de la tastatură a dimensiunii şi elementelor sale.
Declaraţia este echivalentă cu tip nume=valoare dar, în plus, nu permite modificarea valorii
lui nume printr-o expresie de atribuire nume = valoare_noua;
Faţă de o constantă simbolică, în acest caz se rezervă spaţiu de memorie în care se înscrie va-
loarea constantei (constantă obiect).
Prin această declarare se defineşte un pointer spre o zonă cu valoare constantă. Nu este permi-
să atribuirea de genul *nume=valoare_noua, dar se poate ca variabilei nume să i se atribuie o
adresă (de exemplu, nume = p, unde p este un pointer spre tip). Pentru a modifica valoarea în-
scrisă în memorie la adresa memorată de pointerul nume se poate folosi totuşi un alt pointer:
tip *t;
t=nume;
*t=valoare_noua;
Construcţia de mai sus se foloseşte la declararea parametrilor formali, pentru a împiedica modi-
ficarea lor în corpul subprogramelor, în cazul în care apelatorul are nevoie de valorile iniţiale.
Algoritmi în programare 14
În linia de comandă a unui program pot să apară parametri (sau argumente). Aceştia
sînt şiruri de caractere despărţite prin spaţii. Programul poate accesa argumentele prin inter-
mediul parametrilor predefiniţi ai funcţiei main:
Exemplu
15. Dacă programul nu are nici un parametru, argc are valoarea 1, dacă programul are
doi parametri, argc are valoarea 3 etc.
Variabila argv este un vector de pointeri care conţine adresele de memorie unde s-au
stocat şirurile de caractere care constituie parametrii programului. Primul şir (cu adresa
argv[0]) conţine identificatorul fişierului (inclusiv calea completă) care memorează progra-
mul executabil. Următoarele şiruri conţin parametrii în ordinea în care au apărut în linia de
comandă (parametrii în linia de comandă sînt şiruri de caractere separate prin spaţii). Interpre-
tarea acestor parametri cade în sarcina programului.
Exemplu
16. Să se scrie un program care afişează parametrii din linia de comandă.
#include<stdio.h>
main(int argc, char *argv[])
{ int i;
printf("Fisierul executabil: %s\n", argv[0]);
for(i=1;i<argc;i++)
printf("Parametrul nr. %d: %s\n",i, argv[i]);
}
Teste de autoevaluare
5. Să se scrie o un program care preia din linia de comandă doua valori întregi, le
adună şi afişează cele două valori şi suma lor. Dacă în linia de comandă nu se primesc
doi parametri întregi, se va afişa un mesaj de eroare..
4.
int i;
printf("Nr. elemente: ");
scanf("%d ", &n);
*v=(float*)malloc(*n*sizeof(float));
for(i=0;i<n;i++)
{ printf("v(%d)= ",i);
scanf("%f",&(*v)[i]); // sau scanf("%f", v+i);
}
Rezumat
În cadrul acestei unităţi de învăţare au fost studiate următoarele aspecte ale programă-
rii calculatoarelor cu privire la tipurile dinamice de date:
definiţia tipurilor de date pointer;
definirea tipurilor de date pointer în C, declararea şi iniţializarea variabilelor de tip
pointer;
utilizarea pointerilor, operaţii aritmetice cu pointeri;
alocarea dinamică a datelor:
alocarea dinamică a masivelor de date şi accesarea elementelor componente;
protejarea variabilelor statice şi dinamice împotriva modificării, prin intermediul mo-
dificatorului const;
preluarea şi prelucrarea parametrilor din linia de comandă prin intermediul pointerilor
argc şi argv.
După încheierea studierii acestei unităţi de învăţare, studenţii sînt au cunoştinţele şi
abilităţile necesare lucrului cu pointeri şi date alocate dinamic, abilităţi necesare în continuare,
pentru abordarea aspectelor mai complexe ale programării calculatoarelor.
2. Subprograme
Cuprins
După studierea acestei unităţi de învăţare, studenţii vor avea cunoştinţe teoretice şi
abilităţi practice necesare pentru lucrul cu subprograme. Ei vor putea analiza pro-
blemele şi construi subprogramele care le rezolvă corect. Concret, se vor asimila cu-
noştinţe şi abilităţi de lucru privind:
tipurile de subprograme;
structura şi apelul subprogramelor:
transferul parametrilor între apelator şi apelat;
lucrul cu parametri de tip simplu, masiv, subprogram;
lucrul cu liste de parametri cu lungime variabilă;
lucrul cu subprograme recursive.
tip nume([lista-parametri-formali])
unde:
tip poate fi un tip simplu de dată. Dacă lipseşte, este considerat tipul implicit (int pen-
tru unele compilatoare, void pentru altele);
nume este un identificator care reprezintă numele funcţiei;
lista-parametrilor-formali conţine parametrii formali sub forma:
Parametrii sînt separaţi prin virgulă. La limită, lista poate fi vidă. Pentru fiecare para-
metru trebuie specificat tipul, chiar dacă mai mulţi parametri sînt de acelaşi tip (nu este posi-
bilă definirea de liste de parametri cu acelaşi tip).
Pentru funcţiile care nu întorc o valoare prin numele lor, tipul funcţiei va fi void sau va
fi omis.
return(expresie); sau
return expresie; sau
return;
Prima şi a doua formă sînt folosite în cazul funcţiilor care returnează o valoarea prin
numele lor. Prin executarea acestei instrucţiuni se evaluează expresia, valoarea sa este atribui-
tă funcţiei şi se încheie execuţia funcţiei. A treia formă este folosită în cazul funcţiilor care nu
returnează nici o valoare prin numele lor (poate chiar să lipsească). Dacă este prezentă, efectul
ei este încheierea execuţiei funcţiei.
Tipul expresiei din instrucţiunea return trebuie să coincidă cu tipul funcţiei.
Atenţie: corect este tipul rezultatului întors de funcţie prin numele său. Vom vedea
mai tîrziu că sintagma tipul funcţiei are un alt înţeles, mai complex.
În limbajul C nu este admisă imbricarea, adică definirea unui subprogram în cadrul al-
tui subprogram şi nu sînt permise salturi cu instrucţiunea goto (instrucţiune de salt necondiţi-
onat) în afara subprogramului curent.
Declararea unui subprogram apare, în cadrul fişierului sursă, înaintea primului apel.
Există cazuri particulare în care, fie funcţiile se apelează unele pe altele (de exemplu, cazul
recursivităţii mutuale), fie definiţia nu se află în fişierul sursă. Pentru a oferi compilatorului
posibilitatea să efectueze verificarea validităţii apelurilor, sînt prevăzute declaraţii ale subpro-
gramelor fără definirea corpului lor (declaraţii anticipate). Aceste declaraţii se numesc proto-
tipuri şi apar în afara oricărui corp de funcţie. Sintaxa generală este:
tip nume ([lista-parametri-formali]);
Prototipul este de fapt un antet de funcţie după care se scrie caracterul ; (punct şi vir-
gulă). Numele parametrilor pot lipsi, fiind suficientă specificarea tipurilor lor. Prototipul tre-
buie inserat în program înaintea primului apel al funcţiei. Domeniul de valabilitate a declara-
ţiei unui subprogram este limitat la partea care urmează declaraţiei din fişierul sursă.
Algoritmi în programare 18
Prototipurile funcţiilor standard se află în fişiere header (cu extensia .h). Utilizarea
unei funcţii din bibliotecă impune includerea fişierului asociat, cu directiva #include.
Exemple
1. Să se scrie o funcţie care calculează cel mai mare divizor comun dintre două nume-
re întregi nenule, utilizînd algoritmul lui Euclid şi un apelator pentru testare.
#include <stdio.h>
void main()
{ int n1,n2;
printf("Numerele pentru care se va calcula cmmdc:");
scanf("%d%d",&n1,&n2);
if(n1&&n2) printf("\ncmmdc=%d",cmmdc(n1,n2));
else printf("Numerele nu sînt nenule!");
}
#include <stdio.h>
void main()
{ int n1,n2;
printf("Numerele pentru care se va calcula cmmdc:");
scanf("%d%d",&n1,&n2);
if(n1&&n2) printf("\ncmmdc=%d",cmmdc(n1,n2));
else printf("Numerele nu sînt nenule! ");
}
Teste de autoevaluare
1. Care este diferenţa teoretică dintre un subprogram de tip funcţie şi un subprogram
de tip procedură?
2. Ce tipuri de subprograme pot fi scrise în limbajul C?
3. Ce este un prototip?
* Material didactic pentru ID * 19
În practica programării, s-au conturat două posibilităţi de transfer al datelor între ape-
lator şi apelat: prin parametri şi prin variabile globale. Prin utilizarea variabilelor globale nu
se face un transfer propriu-zis, ci se folosesc în comun anumite zone de memorie. Această
practică este nerecomandată.
Exemplu
3. tip_returnat nume(tip_parametru p); Ù p este transferat prin valoare
Folosind transferul prin valoare se pot transmite numai parametri de intrare în subpro-
gram. Pentru a putea folosi parametri de ieşire trebuie simulat transferul prin adresă. În acest
scop, se vor efectua explicit operaţiile care se fac automat la transferul prin adresă din alte
limbaje: se transmite ca parametru adresa parametrului real iar în subprogram se lucrează cu
indirectare (pentru a accesa valoarea parametrului, se dereferenţiază adresa primită).
Exemplu
4. tip_returnat nume(tip_parametru *p); Ù p este transferat prin valoare, fiind
adresa parametrului real.
Pentru parametrii de tip masiv, simularea transferului prin adresă se face în mod im-
plicit, datorită modului de construire a masivelor în C: numele masivului este un pointer. La
apel, în stivă se va transfera adresa masivului iar referirea elementelor se face automat prin
calcul de adrese (vezi capitolul Tipuri dinamice de date).
Următoarele prototipuri sînt echivalente:
tip_returnat nume1(float v[], int n);
tip_returnat nume2(float *v, int n);
Exemple
5. Să se calculeze produsul scalar dintre doi vectori. Rezultatul se întoarce prin nume-
le funcţiei:
float ps(float x[], float y[], int n)
{ int i,prod=0;
for(i=0;i<n;prod+=x[i]*y[i++]);
return prod;
}
În a doua variantă de rezolvare, rezultatul se întoarce prin parametru, simulînd transferul prin
adresă:
6. Să se calculeze elementul maxim dintr-un vector şi poziţiile tuturor apariţiilor acestuia (v, n
sînt parametri de intrare; max, nr_ap, poz sînt parametri de ieşire).
void maxim(float *v, int n, float *max, int *nr_ap, int *poz)
Observaţie: Deşi un tablou (masiv) nu poate fi returnat ca tip masiv prin numele unei
funcţii, se pot scrie funcţii care returnează prin nume un tablou ca pointer – deoarece
numele tabloului este echivalent în C cu adresa sa (pointer la începutul masivului).
Unui astfel de masiv i se alocă memorie în funcţia care îl calculează. Rezultatul întors prin
numele funcţiei este adresa spaţiului de memorie alocat pentru masiv.
* Material didactic pentru ID * 21
Exemple
8. Să se calculeze produsul dintre o matrice şi un vector.
#include<malloc.h>
……………………
float * prod(float a[][30], float v[],int m, int n)
{ float *p;int i,j;
p=(float *)malloc(sizeof(float)*m);
for(i=0;i<m;i++)
for(p[i]=0,j=0;j<n;j++) p[i]+=a[i][j]*v[j];
return p;
}
Teste de autoevaluare
4. Să se realizeze un program C pentru ridicarea unei matrice la o putere. Pentru
aceasta se folosesc două funcţii care returnează, prin pointeri, produsul a două matrice
(înmulţire), respectiv ridicarea unei matrice la o putere (putere).
Exemple
9. Fie un subprogram care calculează suma elementelor unui vector v de lungime n.
Subprogramul suma calculează suma elementelor vectorului dar aceasta nu poate fi fo-
losită de apelator, deoarece valoarea sumei este cunoscută numai în interiorul funcţiei (para-
metrul a fost transmis prin valoare). În apelator valoarea variabilei corespunzătoare parame-
trului formal s nu va fi modificată. Pentru ca subprogramul să fie utilizabil, trebuie ca parame-
trul s să fie un pointer spre variabila în care se va memora suma elementelor vectorului:
La apelul funcţiei, primul parametru actual este adresa variabilei în care se memorează suma:
void main()
{ float x, m[20]; int n;
//…
suma(&x, m, n);
//…
}
10. Să se realizeze un subprogram care citeşte de la tastatură o valoare întreagă care aparţine
unui interval dat.
Exemplu
11.
Atenţie: este impropriu spus transfer prin variabile globale, deoarece nu se realizează
un transfer propriu-zis. De fapt o zonă de memorie este accesată de mai multe entităţi
(subprograme), toate avînd posibilitatea utilizării şi modificării valorii din acea zonă.
Datorită lipsei controlului asupra modificărilor, acest mod de transmitere a datelor nu este re-
comandat. Se prefera utilizarea acestei metode doar atunci cînd este vorba de valori comune,
general valabile într-o aplicaţie şi care se modifică relativ rar (de exemplu calea către fişierul
de date cu care se lucrează).
Teste de autoevaluare
5. Cum se realizează fizic transferul unui parametru prin valoare, respectiv prin adre-
să? Care este efectul asupra proiectării subprogramelor?
În limbajul C, numele unei funcţii este un pointer care indică adresa de memorie unde
începe codul executabil al funcţiei. Aceasta permite transmiterea funcţiilor ca parametri în
subprograme precum şi lucrul cu tabele de funcţii. În acest scop trebuie parcurse următoarele
etape:
unde nume_var este o variabilă de tip procedural şi are tipul „pointer spre funcţie cu parame-
trii lista_parametri_formali şi care returnează o valoare de tipul tip_rezultat”. Lui nume_var i
se poate atribui ca valoare doar numele unei funcţii de prototip corespunzător acestui tip:
tip_rezultat nume_f(lista_parametrilor_formali);
tip_rezultat nume_functie(lista_parametrilor_formali)
{ … }
void main()
{ …
f(…, nume_functie, …);
}
Algoritmi în programare 24
Exemplu
12. Fie o funcţie care efectuează o prelucrare asupra unui vector. Nu se cunoaşte apri-
ori tipul prelucrării, aceasta fiind descrisă de o altă funcţie, primită ca parametru. Pot exista
mai multe funcţii care descriu prelucrări diferite asupra unui vector şi oricare din ele poate fi
transmisă ca parametru.
Apelul se realizează prin transmiterea ca parametru real a funcţiei potrivite prelucrării dorite.
void main()
{ float tab[10]; int m,i;
printf("Numarul de elemente(<10): ");
scanf("%d ", &m);
for(i=0,i<m;i++)
{printf("a(%d)=",i);
scanf("%f",&tab[i]);
}
printf("Se calculeaza suma elementelor…\n");
printf("Rezultatul prelucrarii este: %5.2f\n", functie(tab, m, suma));
printf("Se calculeaza media elementelor…\n");
printf("Rezultatul prelucrarii este: %5.2f\n", functie(tab, m, suma));
return;
}
Limbajul C permite lucrul cu variabile de tip pointer, care conţin adresa de început a
unei funcţii (a codului său executabil). Aceste variabile permit transferul adresei funcţiei aso-
ciate ca parametru, precum şi apelul funcţiei prin intermediul pointerului său.
Următoarea declaraţie defineşte pointer_f ca „pointer spre funcţia cu rezultatul
tip_returnat şi parametrii parametri”.
tip_returnat (*pointer_f)([parametri])
Adresa unei funcţii se obţine prin simpla specificare a identificatorului acesteia (fără
specificarea parametrilor sau parantezelor) şi poate fi atribuită unui pointer spre funcţie cu re-
zultat şi parametri compatibili. Pointerul poate fi folosit ulterior pentru apelul funcţiei sau
transmis ca parametru real în apelul unui subprogram care conţine, în lista parametrilor for-
mali, un pointer la un prototip de funcţie compatibilă.
* Material didactic pentru ID * 25
Exemplu
13. Să se aproximeze soluţia unei ecuaţii de forma f(x)=0 prin metoda bisecţiei.
#include<stdio.h>
#include<conio.h>
#include<math.h>
/* functia principala*/
void main()
{ float a,b,eps,x;
int cod;
long n;
float (*functie)(float);
clrscr();
printf("Introduceti capetele intervalului:");
scanf("%f%f",&a,&b);
printf("\nEroarea admisa:");
scanf("%f",&eps);
printf("\nNumarul maxim de iteratii:");
scanf("%li",&n);
functie=fct;
bisectie(a,b,functie,eps,n,&cod,&x);
if(!cod)
printf("\nNu se poate calcula solutia aproximativa");
else
printf("\n Solutia aproximativa este: %f",x);
}
Exemplu
14. Să se sorteze un şir cu elemente de un tip neprecizat, dar pe care se poate defini o
relaţie de ordine (de exemplu numeric, şir de caractere, caracter).
Metoda aleasă spre exemplificare este sortarea prin selecţie directă. Un subprogram de
sortare care să nu depindă de tipul elementelor şi de criteriul de sortare considerat trebuie să
aibă ca parametri formali:
vectorul de sortat, ca pointer la tipul void, asigurîndu-se astfel posibilitatea realizării
operaţiei de schimbare a tipului („cast”) în funcţie de necesităţile ulterioare (la mo-
mentul apelului se poate realiza modificarea tipului void* în tip_element*, unde
tip_element reprezintă tipul elementelor vectorului de sortat);
Algoritmi în programare 26
Fişierul sursă care conţine funcţia de sortare descrisă anterior este următorul:
//fisier exp_tip.cpp
#include <mem.h>
include<alloc.h>
#include <stdio.h>
#include <string.h>
#include<conio.h>
#include "exp_tip.cpp"
int compara(const void *a, const void *b)
{ if(strcmp((char *)a, (char *)b)>0)return 1;
else return 0; }
void main()
{ typedef char cuvant[10];
cuvant vect[20];
int n;
clrscr();
printf("Dimensiunea vectorului de cuvinte:");
scanf("%d",&n);
printf("\nCuvintele:");
for(int i=0;i<n;i++)
scanf("%s",&vect[i]);
sort(vect,n,10,compara);
printf("\nCuvintele sortate:");
for(i=0;i<n;i++)
printf("\n%s",vect[i]);
getch();
}
Teste de autoevaluare
6. Să se modifice exemplul de mai sus privind rezolvarea unei ecuaţii prin metoda bi-
secţiei astfel încît să se detecteze şi situaţia găsirii soluţiei exacte a ecuaţiei.
7. Folosind exemplul implementării metodei bisecţiei, scrieţi un subprogram care implemen-
tează metoda tangentei pentru rezolvarea unei ecuaţii. Scrieţi şi un program apelator, în care
să testaţi subprogramul.
va_list este un pointer către lista de parametri. În funcţia utilizator corespunzătoare trebuie
declarată o variabilă (numită în continuare ptlist) de acest tip, care va permite adresarea para-
metrilor.
va_start iniţializează variabila ptlist cu adresa primului parametru din sublista variabilă.
Prototipul acestei funcţii este:
void va_start(va_list ptlist, ultim);
unde ultim reprezintă numele ultimului parametru din sublista variabilă. În unele situaţii (vezi
exemplele) se transferă în acest parametru numărul de variabile trimise.
va_arg întoarce valoarea parametrului următor din sublista variabilă. Prototipul acestei
funcţii este:
Exemplu
15. Să se calculeze cel mai mare divizor comun al unui număr oarecare de numere în-
tregi.
#include<stdio.h>
#include<conio.h>
#include<stdarg.h>
void main()
{ int x,y,z,w;
clrscr();
scanf("%d%d%d%d",&x,&y,&z,&w);
printf("\nCmmdc al primelor 3 numere:%d\n",cmmdc_var(3,x,y,z));
printf("\nCmmdc al tuturor numerelor:%d\n",cmmdc_var(4,x,y,z,w));
}
int cmmdc(int x,int y) //cel mai mare divizor comun a doua numere
{ int d=x,i=y,r;
do{ r=d%i;
d=i;i=r;
}
while(r);
return d;
}
for(int i=1;i<nr;i++)
{ //extragerea urmatorului element din lista de parametri
y=va_arg(ptlist,int);
z=cmmdc(x,y);x=z;
}
va_end(ptlist);
return x;
}
Exemplu
#include<stdarg.h>
#include<stdio.h>
#include<conio.h>
void main()
{ int n1,n2,n3,n4;
double x1[10],x2[10],x3[10],x4[10],z[50];
clrscr();
scanf("%d%d%d%d",&n1,&n2,&n3,&n4);
for(int i=0;i<n1;i++)
scanf("%lf",&x1[i]);
for(i=0;i<n2;i++)
scanf("%lf",&x2[i]);
for(i=0;i<n3;i++)
scanf("%lf",&x3[i]);
for(i=0;i<n4;i++)
scanf("%lf",&x4[i]);
inter_var(z,4,x1,n1,x2,n2);
printf("\nRezultatul interclasarii primilor 2 vectori\n");
for(i=0;i<n1+n2;i++)
printf("%lf ",z[i]);
inter_var(z,8,x1,n1,x2,n2,x3,n3,x4,n4);
printf("\nRezultatul interclasarii celor 4 vectori\n");
for(i=0;i<n1+n2+n3+n4;i++)
printf("%lf ",z[i]);
}
void inter(double *x, int n1, double *y, int n2, double *z)
{ int i,j,k;
for(i=0,j=0,k=0;(i<n1)&&(j<n2);k++)
if(x[i]<y[j])
z[k]=x[i++];
else z[k]=y[j++];
if(i<n1)
for(;i<n1;z[k++]=x[i++]);
else for(;j<n2;z[k++]=y[j++]);
}
inter(x1,n1,y,n2,z);
for(j=0;j<n1+n2;j++)
x1[j]=z[j];n1+=n2;
}
va_end(ptlist);
}
Algoritmi în programare 30
Teste de autoevaluare
8. Să se scrie o funcţie cu număr variabil de parametri care calculează produsul unui
şir de maxim n matrice. Funcţia trebuie să aloce spaţiu de memorie pentru masivul re-
zultat şi să trimită către apelator adresa acestui spaţiu, împreună cu dimensiunile masivului
rezultat şi un cod de eroare (nu orice şir de matrice se pot înmulţi). Funcţia primeşte ca para-
metri numărul de matrice care participă la operaţie, adresele şi dimensiunile lor, in ordinea:
adresă matrice, nr. linii, nr. coloane, adresă matrice, nr. linii, nr. coloane … .
Exemplu
17. Calculul valorii n! pentru n dat poate fi efectuat pe baza formulei recursive
⎧1, n = 0
n! = ⎨ .
⎩n(n − 1)! , n > 0
Fie fact(n) funcţia C care calculează n!. Dacă n ≥ 1, evaluarea lui fact(n) rezultă prin
multiplicarea cu n a valorii calculate de apelul fact(n-1), cu fact(0)=1. Cu alte cuvinte, apelul
funcţiei fact(n) realizează calculul „imediat” dacă n=0, altfel presupune un nou apel al acele-
iaşi funcţii pentru valoarea argumentului decrementată. Cazurile în care este posibilă evalua-
rea „imediată” se numesc condiţii de terminare.
În limbajul C, funcţia fact este
long fact(unsigned n)
{ if (!n) return 1;
return n*fact(n-1);
}
Exemplu
n!
18. Utilizarea formulei C nk = pentru calculul combinărilor ( n , k ∈ N date)
k ! (n − k )!
este ineficientă şi uneori imposibilă deoarece n!, pentru n ≥ 13 nu poate fi reprezentat în calcu-
lator ca dată de un tip întreg, chiar dacă numărul C nk este relativ mic şi poate fi reprezentat
prin intermediul unui tip întreg. Pe baza relaţiei de recurenţă C nk = C nk−1 + C nk−−11 , valoarea C nk
poate fi calculată astfel. Fie comb(n,k) funcţia care calculează C nk . Conform relaţiei de recu-
renţă, dacă n ≥ k ≥ 1, atunci evaluarea corespunzătoare apelului comb(n,k) revine la însumarea
rezultatelor obţinute prin apelurile comb(n-1,k) şi comb(n-1, k-1), unde comb(n,0)=1, n ≥ 0.
Dacă evaluările comb(n-1,k) şi comb(n-1, k-1) sînt realizate în acelaşi mod, rezultă că apelul
comb(n,k) va determina o secvenţă de apeluri ale aceleiaşi funcţii pentru valori ale argumente-
lor din ce în ce mai mici, pînă cînd este îndeplinită una din condiţiile terminale comb(n,0)=1,
comb(k,k)=1.
* Material didactic pentru ID * 31
Mecanismul prin care este efectuat apelul unui subprogram se bazează pe utilizarea
stivei memoriei calculatorului. Fiecare apel determină introducerea în stivă a valorilor para-
metrilor reali, a adresei de revenire şi a variabilelor locale. La momentul execuţiei, aceste in-
formaţii sînt extrase cu eliminare din stivă, eliberîndu-se spaţiul ocupat.
În cazul subprogramelor recursive, mecanismul funcţionează astfel: este generat un
număr de apeluri succesive cu ocuparea spaţiului din stivă necesar efectuării apelurilor pînă la
îndeplinirea unei condiţii de terminare; apelurile sînt executate în ordinea inversă celei în care
au fost generate, iar operaţia de inserare în stivă poate produce depăşirea spaţiul de memorie
rezervat.
Fact=3*Fact(2)
Fact=1*Fact(0) Fact=1
Fact=2*Fact(1)
2 1
3
(o)
Fact=3*Fact(2)
(o)
1 2
Fact=1 Fact=2
2 3
Fact=2*Fact(1) Fact=3*Fact(2)
3
3
Fig. 2.1.b Eliberarea stivei după execuţia de-
Fact=3*Fact(2) terminată de condiţia de terminare
Fact=6
(o)
(o)
Apelurile recursive ale unui subprogram S1 pot fi şi indirecte, în sensul că este efectu-
at un apel al unui alt subprogram S2 şi S2 iniţiază un apel al lui S1 (recursivitate mutuală).
Exemplu
19. De exemplu, să se calculeze valorie funcţiei h=f◦g◦f , unde f,g:R→R sînt funcţii
date. Pentru funcţiile f, g definite prin
⎧2 x 3 + 1, x < 5
⎪ ⎧⎪5 x 2 − 3 x + 2 , x ≤ 1
f (x ) = ⎨ x 4 + 2 , 5 ≤ x < 8 , g (x ) = ⎨ 3
⎪3 , x > 8 ⎪⎩ x − x + 5 , x > 1
⎩
funcţiile C pentru calculul h=f◦g◦f pot fi descrise astfel:
float f(float x)
{ if (x<5) return 2*pow(x,3)+1;
if (x<8) return pow(x,4)+2;
return 3;
}
float g(float x)
{ if (x<=1) return 5*x*x-3*x+2;
return pow(x,3)-x+5;
}
float h(float x)
{ return f(g(f(x)));
}
Exemplu
21. Calculul valorii funcţiei Ackermann.
Funcţia Ackermann este definită pentru argumentele m,n numere naturale prin
⎧n + 1, m = 0
⎪
a (m , n ) = ⎨a (m − 1,1), n = 0
⎪a (m − 1, a (m , n − 1)), altfel
⎩
Funcţia C Ackermann calculează valoarea funcţiei a pentru m, n parametri naturali daţi.
long Ackermann(unsigned m, unsigned n)
{ if (!m) return n+1;
if (!n) return Ackermann(m-1,1);
return Ackermann(m-1,Ackermann(m,n-1));
}
Exemplu
22. Problema calculului celui mai mare divizor comun dintre două numere naturale a
şi b poate fi rezolvată recursiv, conform definiţiei următoare,
⎧a , a = b
(a ,b ) = ⎪⎨( a − b ,b ), a > b
⎪( a ,b − a ), b > a
⎩
Funcţia C cmmdc(a,b) este
long cmmdc(long a, long b)
{ if (a==b) return a;
if (a>b) return cmmdc(a-b,b);
return cmmdc(a,b-a);
}
Exemplu
23. Problema turnurilor din Hanoi ilustrează foarte bine avantajele recursivităţii. Pro-
blema poate fi enunţată astfel: fie trei tije a, b, c; pe tija a sînt plasate n discuri de di-
ametre diferite, în ordinea descrescătoare a acestora (de jos în sus). Se cere ca cele n discuri
de pe tija a să fie deplasate pe tija c astfel încît să fie îndeplinite condiţiile:
- la fiecare mutare este deplasat unul dintre discurile aflate pe poziţia superioară pe una din
tije;
- oricare din discuri poate fi aşezat numai pe un disc de diametru mai mare;
- tija b poate fi folosită pentru deplasări intermediare.
void main()
{ unsigned n,a,b,c;
clrscr();
printf("n=");scanf("%u",&n);
Hanoi(n,1,2,3);getch();
}
Exemplu
24. Sortarea crescătoare prin inserare
Pentru sortarea crescătoare a unei secvenţe de numere reale se poate raţiona astfel: dacă P(n)
este problema sortării crescătoare a secvenţei a1, a2, …, an şi P(n-1) este problema sortării
primelor n-1 componente, atunci soluţia problemei P(n) rezultă din soluţia problemei P(n-1)
prin inserarea lui an în soluţia problemei P(n-1). Fiecare problemă intermediară P(k),
k = 2 ,..., n este rezolvată aplicînd aceeaşi metodă P(1) este o problemă „gata rezolvată” (con-
diţie terminală).
Funcţia insera realizează inserarea valorii x în vectorul v în poziţia „corectă”. Funcţia
recursivă inssort realizează sortarea vectorului cu n componente prin inserţie.
Exemplu
25. Pot fi realizate desene prin compunerea într-o manieră recursivă a unor figuri ge-
ometrice primitivă (de bază). Compunerea constă în repetarea primitivelor considerate şi a re-
zultatelor obţinute prin rotirea lor într-un sens sau celălalt. Astfel, dacă mulţimea de primitive
H0 constă dintr-un punct şi pentru compunere este considerat un segment de lungime h,
atunci: H1 rezultă din patru exemple (copii, realizări, instanţe, clone) de primitive din H0 unite
prin segmente de lungime h; H2 rezultă din 16 exemple din H0 unite prin 15 segmente de lun-
gime h/2 ş.a.m.d. De asemenea, H2 se poate obţine prin interconectarea a patru copii ale lui H1
rotite cu unghiuri drepte şi prin interconectarea punctelor izolate prin segmente de aceeaşi
lungime. Generalizînd, o curbă Hn rezultă din patru copii ale unei curbe Hn-1, punctele izolate
fiind unite prin segmente de lungime hn=h/2n. Curbele rezultate se numesc curbele Hilbert
Hi, i ≥ 0.
H1 H2 H3
Dacă cele patru părţi ale unei curbe Hilbert Hk sînt notate A, B, C, D şi se reprezintă
prin săgeţi rutinele care desenează segmentele care le interconectează, atunci rezultă următoa-
rele scheme recursive.
A: D ← A↓ A→ B
A: D ← A↓ A→ B
B: C↑B→B↓ A
C: B→C ↑C ← D
D: A↓ D ← D↑C
Prin executarea următoarei surse C sînt obţinute curbele Hilbert H4 (exemplul a fost
scris în mediul Borland C 3.11).
#include <stdio.h> x=x0;y=y0;moveto(x,y);
#include <graphics.h> A(i); }
#include <stdlib.h> while(i<n);
#include <conio.h> getch();
closegraph();
const n=5; }
const h0=480;
int i=0; void A(int i)
int h; { if (i>0)
int x,y,x0,y0,gm; { D(i-1);x-=h;lineto(x,y);
int gd=DETECT; A(i-1);y-=h;lineto(x,y);
A(i-1);x+=h;lineto(x,y);
void A(int); B(i-1);
void B(int); }
void D(int); }
void C(int);
void B(int i)
void main() { if (i>0)
{ clrscr(); { C(i-1);y+=h;lineto(x,y);
initgraph(&gd,&gm,"D:\BC\BGI"); B(i-1);x+=h;lineto(x,y);
setbkcolor(0); B(i-1);y-=h;lineto(x,y);
setcolor(4); A(i-1);
h=h0;y0=x0=h/2; }
do{ i++;h/=2; }
x0+=h/2;y0+=h/2;
Algoritmi în programare 36
void C(int i)
{ if (i>0)
{ B(i-1);x+=h;lineto(x,y);
C(i-1);y+=h;lineto(x,y);
C(i-1);x-=h;lineto(x,y);
D(i-1);
}
}
void D(int i)
{ if (i>0)
{ A(i-1);y-=h;lineto(x,y);
D(i-1);x-=h;lineto(x,y);
D(i-1);y+=h;lineto(x,y);
C(i-1);
}
}
Exemplu
26. În cazul curbelor Hilbert, toate unghiurile determinate de segmentele care unesc
punctele sînt de măsură 900. Dacă se consideră ca valori pentru măsurile unghiurilor determi-
nate de aceste segmente 450, 900, 1350, rezultă curbele Sierpinski Sn, n ≥ 1.
Curba lui Sierpinski pentru n=2 este următoarea:
lineto(x,y); { if (i>0)
B(i-1);x+=2*h; { C(i-1);x-=h;y+=h;
lineto(x,y); lineto(x,y);
D(i-1);x+=h;y+=h; D(i-1);x-=2*h;
lineto(x,y); lineto(x,y);
A(i-1); B(i-1);x-=h;y-=h;
} lineto(x,y);
} C(i-1);
}
void B(int i) }
{ if (i>0) void D(int i)
{ B(i-1);x-=h;y-=h; { if (i>0)
lineto(x,y);C(i-1); { D(i-1);x+=h;y+=h;
y-=2*h; lineto(x,y);
lineto(x,y); A(i-1);y+=2*h;
A(i-1);x+=h;y-=h; lineto(x,y);
lineto(x,y); C(i-1);x-=h;y+=h;
B(i-1); lineto(x,y);
} D(i-1);
} }
}
void C(int i)
Teste de autoevaluare
9. Să se scrie un subprogram recursiv pentru calcularea sumei elementelor unui vec-
tor.
10. Să se scrie un subprogram recursiv pentru determinarea elementului minim dintr-un vec-
tor.
11. Să se scrie un subprogram recursiv pentru determinarea elementului minim şi a elementu-
lui maxim dintr-un vector.
12. Să se scrie un subprogram recursiv pentru determinarea elementelor şirului lui Fibonacci.
13. Scrieţi un subprogram pentru rezolvarea problemei căutării binare în vectori sortaţi. Fie v
un vector de numere reale sortat crescător şi k un număr real dat. Să se identifice (dacă există)
o valoare poz, astfel încît v[poz]=k.
3.
#include<stdio.h>
#include<conio.h>
#include<alloc.h>
void main()
{ int i,j,p,n,l,m; float **a,**ap,f;
clrscr();
printf("\n n=");
scanf("%i",&n);
a=(float **)malloc(n*sizeof(float *));
for(i=0;i<n;i++)
*(a+i)=(float *)malloc(n*sizeof(float));
for(i=0;i<n;i++)
for(j=0;j<n;j++)
{ scanf("%f ",&f);
*(*(a+i)+j)=f;
}
scanf("%i",&p);
ap=putere(a,p,n);
for(i=0;i<n;i++)
{ for(j=0;j<n;j++)
printf("%f ",*((*(ap+i)+j)));
printf("\n");
}
getch();
}
13.
#include <stdio.h>
#include <conio.h>
int cauta_binar(float *,int,int,float);
void main()
{ clrscr();
printf("Dimensiunea vectorului:");
int n;
scanf("%i",&n);
* Material didactic pentru ID * 39
printf("Elementele vectorului\n");
float v[100];
for(unsigned i=0;i<n;i++)
scanf("%f",&v[i]);
printf("Cheia de cautare:");
float k;
scanf("%f",&k);
int c=cauta_binar(v,0,n-1,k);
if(c==-1)
printf("Cheia nu a fost gasita");
else printf("Cheia pe pozitia:%i",c);
getch();
}
Rezumat
În cadrul acestei unităţi de învăţare au fost studiate următoarele aspecte ale programă-
rii calculatoarelor cu privire la lucrul cu subprograme:
cunoştinţe teoretice despre subprograme, în general şi în limbajul C;
construcţia şi apelul subprogramelor în limbajul C;
transferul datelor între apelator şi apelat, prin variabile globale şi prin parametri (prin
valoare şi prin simularea transferului prin adresă);
pointeri la funcţii şi trimiterea funcţiilor ca parametri către alte funcţii;
funcţii cu număr variabil de parametri;
subbrograme recursive.
După încheierea studierii acestei unităţi de învăţare, studenţii au cunoştinţele şi abilită-
ţile necesare lucrului cu subprograme în vederea rezolvării problemelor complexe de progra-
mare prin rezolvarea separată a subproblemelor componente.
3. Articolul şi fişierul
Cuprins
După studierea acestei unităţi de învăţare, studenţii vor avea cunoştinţe teoretice şi
abilităţi practice necesare pentru lucrul cu structuri de date interne eterogene şi cu
structuri de date externe (fişiere de date). Concret, se vor asimila cunoştinţe şi abili-
tăţi de lucru privind:
tipul de dată articol;
tipurile de fişiere de date;
metode de organizare a fişierelor şi tipuri de acces la datele conţinute;
utilizarea articolelor interne pentru prelucrarea fişierelor de date;
operaţii generale de prelucrare a fişierelor de date.
Articolul este o structură de date eterogenă, cu acces direct la elementele sale, între ca-
re există o relaţie de ordine ierarhică.
Articolul poate fi reprezentat sub formă de arbore, ale cărui noduri sînt asociate com-
ponentelor structurii. Componentele de pe ultimul nivel sînt scalare şi se numesc date elemen-
tare sau cîmpuri. Datele de pe celelalte niveluri, denumite date de grup, se constituie prin
agregarea datelor de pe nivelurile inferioare. Data de grup de cel mai înalt nivel (rădăcina ar-
borelui) corespunde articolului în ansamblu. Conceptual, datele de grup de pe diverse niveluri
au aceleaşi proprietăţi ca şi articolul, ceea ce permite ca această structură să fie construită re-
cursiv, prin descompunerea în structuri cu aceleaşi proprietăţi (figura 4.1).
* Material didactic pentru ID * 41
unde tip_articol este identificatorul asociat tipului articol, iar var1, var2,…, varn sînt identifi-
catorii asociaţi variabilelor de tipul articol declarat.
Unele elemente ale declaraţiei pot lipsi (dar nu toate deodată). Dacă lipsesc elementele
var1, var2,…, varn, atunci tip_articol trebuie să fie prezent, fiind numai o declarare explicită
de tip nou, utilizabil ulterior la alte declarări. Dacă lipseşte tip_articol, atunci trebuie să fie
prezentă lista de variabile (nevidă), caz în care este vorba de o declarare de variabile de tip ar-
ticol, fără însă a declara şi un tip utilizator nou. În continuare, tip_articol este un tip nou de
date, iar var1, var2,…, varn sînt variabile de tipul tip_articol. Variabilele pot fi declarate şi ca
masive, ale căror elemente sînt de tip articol: var1[dim1][dim2]…[dimn].
a)
Descrierea constituie o definire implicită de un nou tip de dată. Este posibilă definirea
explicită a unui nou tip de dată, adăugînd cuvîntul rezervat typedef în faţa declarării (în acest
caz nu mai pot fi declarate simultan şi variabile).
Lista_cimpuri este o înşiruire de declaraţii de cîmpuri separate prin punct şi virgulă,
asemănătoare declaraţiilor de variabile, de forma tip_cimp nume_cimp. Cîmpurile unei struc-
turi pot fi variabile simple, masive sau alte articole. Lista cîmpurilor nu poate fi vidă.
Exemplu
1. Definirea tipului de dată număr complex, a unei variabile simple şi a unui masiv
unidimensional cu elemente de acest tip se poate face în oricare din următoarele variante
(pentru un număr complex se vor reţine partea reală şi partea imaginară):
a) struct COMPLEX{float r,i;}a,b[100];
b) struct COMPLEX{float r,i;};
struct COMPLEX a,b[100];
c) struct COMPLEX{float r,i;};
COMPLEX a,b[100];
d) struct {float r,i;}COMPLEX;
COMPLEX a,b[100];
e) typedef struct {float r,i;} COMPLEX;
COMPLEX a,b[100];
Algoritmi în programare 42
Din punct de vedere practic, utilizarea tipului articol este strîns legată de prelucrarea
fişierelor. În lucrul cu variabilele de tip articol se recomandă declararea identificatorului de
tip. În acest mod, identificatorul de tip articol poate fi folosit în definirea mai multor variabile.
În procesul de descriere a unui articol, arborele se parcurge în preordine (de la rădăcină spre
extremităţi şi de la stînga la dreapta).
Exemplu
2. Pentru exemplele din figura 3.1, declararea poate fi realizată prin definire recursivă,
astfel:
struct tip_data struct persoana
{ unsigned zi; { char nume[30];
char luna[3]; char adresa[50];
int an; }; struct tip_data data_nasterii;
} angajat;
Dacă nu ar fi existat declaraţia tipului articol tip_data, atunci tipul persoana putea fi scris astfel:
struct persoana
{ char nume[30];
char adresa[50];
struct
{ unsigned zi;
char luna[3];
int an;
} data_nasterii;
} angajat;
Exemplu
3. Considerînd declaraţiile anterioare, expresia sizeof(data_nasterii) are valoarea 8,
iar sizeof(angajat) are valoarea 90.
Din punct de vedere fizic, identificatorii cîmpurilor din descrierea articolului reprezin-
tă deplasări faţă de începutul acestuia. Adresa fizică a unui cîmp rezultă din însumarea adresei
articolului cu deplasarea sa. Structura arborescentă a articolelor poate fi exprimată sugestiv şi
prin machete, care evidenţiază componentele, natura, lungimea declarată şi lungimea fizică
ale acestora (figurile 3.2 şi 3.3).
Datele de tip articol pot fi referite în două moduri: global sau pe componente. Referi-
rea globală este permisă numai în operaţia de atribuire, cu condiţia ca ambele variabile (sursă
şi destinaţie) să fie articole de acelaşi tip.
* Material didactic pentru ID * 43
Referirea pe componente (prin numele lor) este o reflectare a faptului că articolul este
o structură cu acces direct. Referirea unor componente de tip articol din structura altui articol
este posibilă numai în operaţia de atribuire, în condiţiile precizate anterior la referirea globală.
În cele ce urmează se are în vedere numai referirea componentelor de tip dată elementară, si-
tuate pe ultimul nivel al structurii.
Referirea cîmpurilor unei structuri se face prin calificare, folosind operatorul . (punct).
În referirea prin calificare, asigurarea identificării unice a cîmpurilor se realizează prin asocie-
rea numelui acestora cu numele articolului care le conţine. Construcţia rămîne la această for-
mă în cazul în care structura are numai două niveluri: articolul şi cîmpurile elementare ale
acestuia.
Exemplu
4. Folosind tipul COMPLEX definit anterior, avem:
a.r , a.i - se referă partea reală, respectiv imaginară a variabilei a
b[10].r - se referă partea reală a celui de-al 11-lea element al vectorului b
#include <string.h>
main()
{ struct articol {char nume[40];
char adresa[30];
int an, luna, zi;}
struct articol pers;
……………
strcpy(pers.nume, "Popescu Ion");
strcpy(pers.adresa, "Bucuresti, Pta. Romana 6");
pers.an=1979; pers.luna=3; pers.zi=15;
}
În articolele cu structură recursivă se realizează calificarea progresivă cu articolele de
pe nivelurile superioare, primul calificator fiind numele articolului rădăcină. În lanţul de cali-
ficări, numele articolului rădăcină este nume de variabilă, celelalte fiind nume de cîmpuri ale
articolului. Dacă anumite componente sînt structuri de date de alte tipuri (de exemplu masive
sau şiruri de caractere), în referirea elementelor lor se aplică, pe lîngă calificare, regulile spe-
cifice acestor structuri.
Exemplu
5. Referirea prin calificare a cîmpurilor articolului angajat de tipul persoana (vezi
exemplele anterioare) se realizează astfel:
angajat.nume;
angajat.adresa;
angajat.data_nasterii.zi;
angajat.data_nasterii.luna;
angajat.data_nasterii.an
În aceste referiri, angajat este identificatorul variabilei articol, celelalte elemente sînt identifi-
catori de cîmpuri. Construcţiile angajat.nume şi angajat.adresa corespund referirii globale a
cîmpurilor respective, care sînt şiruri de caractere. Pentru a referi, de exemplu, primul caracter
din şir, se scrie: angajat.nume[0].
Exemplu
6. Se presupune un articol cu structura din figura 3.2.
Cod Vînzări lunare
Magazin Luna 1 Luna 2 … Luna 12
Întreg real Real … real
2 4 4 … 4
Fig. 3.2. Structura de articol pentru exemplul 6
Algoritmi în programare 44
Exemplu
7. Se presupune un articol cu structura din figura 3.3.
Cu toate că numărul de materii prime utilizate poate fi variabil de la un produs la altul, în des-
crierea articolului se alege valoarea maximă a acestuia:
Exemple
8.
#include <stdio.h>
void main()
{
//exemplul 1
struct persoana
{ char nume[40];
char adresa[30];
struct
{ int zi, luna, an;} datan;
};
//exemplul 2
struct magazin
{ int cod_magazin;
float vanzari_lunare[12];
};
* Material didactic pentru ID * 45
//exemplul 3
struct a { int cod_mat; float norma;};
struct produs
{ int cod_produs;
unsigned char nr_mat;
struct a materii_prime[30];
};
Teste de autoevaluare
1. Clasificaţi tipul de date articol, conform clasificării tipurilor de date.
Exemplu
9. Se presupune un articol cu structura din figura 3.4.
Nume Data naşterii An de Forma de învăţămînt
studiu zi id
bursa valoare loc de muncă data angajării
char[40] zi luna an int char float char[30] zi lună an
Fig. 3.4. Articol cu structură variabilă
Algoritmi în programare 46
#include <stdio.h>
void main()
{ //Declararea articolului cu structura variabila:
struct articol
{ char nume[40];
struct { int zi, luna, an;} datan;
int an_st;
char forma_inv;
union
{ struct {char bursa; float valoare;} zi;
struct {char loc_m[30];
struct {int zi, luna, an;} data_ang;
} id;
} parte_vb;
};
Din punct de vedere fizic, existenţa părţii variabile într-un articol generează, la compi-
lare, deplasări egale faţă de începutul articolului pentru toate variantele de descriere. Astfel,
pentru descrierea din exemplul de mai sus se generează deplasarea 49 faţă de începutul artico-
lului, atît pentru cîmpul bursa, cît şi pentru loc_m.
Teste de autoevaluare
2. Descrieţi în limbajul C tipul de dată vehicul corespunzător următoarei reprezentări
tabelare. Alegeţi tipurile şi dimensiunile potrivite pentru fiecare cîmp.
propulsie
viteză nr. lo- umană animală mecanică
masă lungime lăţime
maximă curi tip nr. tip com- consum
nr. roţi cilindree putere
animal animale bustibil (l/100km)
Constantele obiect sînt variabile iniţializate la declarare, pentru care se rezervă me-
morie, dar conţinutul lor nu poate fi modificat pe parcursul programului.
const tip nume_const = {lista_valori};
* Material didactic pentru ID * 47
Exemplu
10. Constantă cu tip.
#include<stdio.h>
void main()
{ struct persoana
{ char nume[40];
char adresa[30];
struct
{ int zi, luna, an;} datan;
};
persoana pers={"Popescu Ion", "Bucuresti; Magheru 14",
{2, 4, 1960}};
//constanta cu tip
pers.datan.zi=4;
}
Exemplu
11. Constantă obiect.
#include<stdio.h>
void main()
{ struct persoana
{ char nume[40];
char adresa[30];
struct {int zi, luna, an;} datan;
};
const persoana pers={"Popescu Ion", "Bucuresti; Magheru 14",
{2, 4, 1960}};
//constanta obiect
// pers.datan.zi=4; genereaza eroare la compilare
}
Teste de autoevaluare
3. Iniţializaţi constante cu tip şi constante obiect de tipul vehicul, pe care l-aţi definit
la tema de autoevaluare nr. 2.
Fişierul reprezintă termenul generic care desemnează structurile de date externe. El es-
te o mulţime (colecţie) de date omogene din punct de vedere al semnificaţiei şi al cerinţelor de
prelucrare. În purtătorul extern, fişierul are, pe lîngă partea de date, şi alte informaţii de identi-
ficare (etichete).
Algoritmi în programare 48
Privit din punctul de vedere al prelucrării, un fişier este o colecţie ordonată de date,
numite articole. Articolul este constituit dintr-o mulţime ordonată de valori ale unor caracte-
ristici ce aparţin, uzual, unei singure entităţi (obiect, fenomen, proces etc.) din domeniul de
activitate abordat. De exemplu, într-un fişier care conţine datele personale ale salariaţilor
dintr-o unitate economică, un articol grupează valorile caracteristicilor unei singure persoane.
Componentele articolului destinate diverselor caracteristici sînt denumite cîmpuri de
date. Depinzînd de natura, ordinul de mărime şi forma de reprezentare externă a valorilor aso-
ciate, fiecare cîmp de date are o lungime, exprimată uzual în octeţi. Lungimea unui articol es-
te dată de suma lungimilor cîmpurilor care îl compun. După cum toate articolele dintr-un fişi-
er au sau nu aceeaşi lungime, se face distincţie între fişierele cu articole de lungime fixă sau
variabilă. Modul de implementare fizică a celor două tipuri de fişiere diferă de la un sistem la
altul şi chiar de la un limbaj la altul.
Pe purtătorul fizic extern, partea de date a fişierului se prezintă ca o succesiune de oc-
teţi cu un conţinut binar fără semnificaţie informaţională. În momentul prelucrării, prin des-
crieri şi operaţii adecvate, din succesiunea memorată extern se „decupează" entităţi (articole,
blocuri, linii sau cîmpuri) cu structuri corespunzătoare prelucrării. Tipul entităţii care se „de-
cupează" depinde de tipul fişierului.
Principiile şi regulile după care se memorează articolele unui fişier pe purtătorul ex-
tern, cu asigurarea protecţiei şi regăsirii acestora, constituie metoda de organizare. În evoluţia
organizării datelor externe s-au cristalizat mai multe metode, dintre care, cele mai uzuale sînt
secvenţială, relativă şi indexată. Principala diferenţiere între metodele de organizare o repre-
zintă tipurile de acces admise.
Tipul de acces reprezintă modalitatea de regăsire (localizare) a articolelor din fişier.
Noţiunea de acces trebuie aplicată atît pentru operaţia de scriere, cît şi pentru cea de citire a
datelor.
Poziţia din/în care se face citirea/scrierea în cadrul fişierului este indicată de un
pointer. Accesul la datele înregistrate pe un purtător tehnic poate fi secvenţial sau direct, în
funcţie de modul în care se stabileşte pointerul.
Accesul secvenţial este posibil la toţi purtătorii tehnici de date şi presupune înscrierea
înregistrărilor în ordinea furnizării lor sau regăsirea în ordinea în care au fost înscrise în suport
(figura 3.5).
P(Ak)=f (P(Ak-1))
Traversare
Pointerul de fişier avansează, în scriere şi citire, de la o entitate (articol, bloc, linie sau
cîmp) la alta. Dacă pointerul se exprimă prin deplasare faţă de începutul fişierului, atunci, ma-
tematic, acest lucru se poate exprima astfel:
P(A1) = 0;
P(Ak) = f(P(Ak-1)) = P(Ak-1)+lartk-1; pentru k=2,n;
unde Ak este articolul k şi lartk este lungimea articolului k.
* Material didactic pentru ID * 49
Accesul direct este posibil numai la fişierele care au o anumită organizare, au ca enti-
tate de transfer articolul sau blocul şi sînt memorate pe discuri magnetice. Accesul direct se
bazează pe existenţa unui algoritm implementat în sistem care asigură regăsirea (localizarea)
articolelor în funcţie de o informaţie de regăsire. Valoarea pointerului este determinată direct,
fără să depindă de valoarea sa anterioară: P(Ak)=f(irk), unde Ak este articolul k, iar irk este o
informaţie de regăsire a articolului k. În funcţie de algoritmul şi informaţia de regăsire, există
două tipuri de acces direct: după cheie şi după numărul relativ al articolului.
În cazul accesului direct după cheie, articolul este regăsit prin aplicarea unui algoritm
asupra unei informaţii de identificare de tip cheie: P(Ak)=f(cheiek). În cazul accesului direct
după numărul relativ - care se mai numeşte, simplu, acces relativ - (figura 3.7), articolul este
localizat în fişier prin numărul său, stabilit, în cadrul fişierului, de la valoarea zero:
P*(Ak)=(k-1); P(Ak)=P*(Ak)×lart. P*(Ak) reprezintă poziţia exprimată în număr relativ, iar
P(Ak) reprezintă poziţia exprimată prin deplasare, în octeţi, faţă de începutul fişierului (la
unele sisteme numărul relativ este stabilit de la unu: P*(Ak)=k).
La scriere, articolul Ak (numărul relativ k-1) se memorează pe poziţia sa, celelalte k-1
articole anterioare putînd să nu existe (pe suport există însă rezervat loc pentru ele). La citire,
articolul Ak (cu numărul relativ k-1, kn) este localizat direct şi conţinutul lui se transferă în
memoria internă.
Teste de autoevaluare
3. Daţi exemple de fişiere aflate pe diferite tipuri de suport extern şi specificaţi ce ti-
puri de acces sînt permise în fiecare caz.
[n:][cale][\]nume_fişier[.extensie] unde
n este numele unităţii de disc (A:, B:, C: etc). Prin lipsă, se consideră unitatea curentă.
cale (path) este calea de acces de la directorul rădăcină pînă la subdirectorul dorit. Fieca-
re nume de director (subdirector) din interiorul căii este precedat de caracterul backslash (\).
Prin lipsă, se consideră calea subdirectorului curent. Calea selectată la un moment dat poate
începe de la rădăcină sau de la subdirectorul curent. Cînd calea începe cu caracterul backslash
(\) căutarea începe de la rădăcină; în caz contrar, căutarea începe de la directorul curent.
nume_fişier este numele extern al fişierului, format din maxim 8 caractere alfanumerice,
mai puţin unele caractere speciale, ca: ." \ / : ' > < + = ; , ). Există o serie de nume prestabilite,
asociate unor dispozitive standard de intrare/ieşire, care nu pot fi utilizate de programator pen-
tru propriile fişiere: CON, AUX, COM1, COM2, LPT1, LPT2, LPT3, NULL, PRN, CLOCK$
(dispozitiv pentru ceasul de timp real).
extensie este formată din maxim trei caractere alfanumerice prin care utilizatorul are po-
sibilitatea să-şi identifice fişiere cu conţinuturi diferite. Prin lipsă nu se asumă nici o valoare.
Fiecare subdirector conţine două intrări speciale marcate prin caracterul ".", respectiv
caracterele ".." în locul numelui de fişier. Prima intrare realizează „autopunctarea”, indicînd
faptul că entitatea este subdirector (nu fişier de date), a doua intrare „punctează” subdirectorul
părinte. În construirea căii de acces se poate folosi succesiunea de două puncte pentru a indica
subdirectorul părinte.
* Material didactic pentru ID * 51
F1 D1 D2 D3 F2 F3
F4 F2 D4 D5 F5 F6 F7
F8 F9
Exemple
12. Pentru structura din figura 3.8:
C:\F1 → Fişierul F1 din rădăcină
C:\D2\F2 → Fişierul F2 din subarborele C:\D2
C:\F2 → Fişierul F2 din directorul rădăcină
C:\D2\D4\F9 → Fişierul F9 din subarborele C:\D2\D4
Pentru a indica fişierul F9 se poate folosi una din scrierile:
C:\D2\D4\F9 → de oriunde
\D2\D4\F9 → de oriunde din unitatea C:
..\D4\F9 → din subdirectorul D5
F9 → din subdirectorul D4.
Exemple
13. Standard MS-DOS:
.COM → program executabil;
.EXE → program executabil;
.SYS → driver de sistem;
.OBJ → program obiect;
.BAT → fişiere de comenzi DOS (prelucrări BATCH).
14. Standarde de firmă:
.ARC → arhivă compactată cu PKPAK sau ARC;
.ZIP → arhivă compactată cu PKZIP sau WINZIP;
.DBF → bază de date DBASE.
15. Formate ASCII:
.ASM → program sursă ASSEMBLER;
.BAS → program sursă BASIC;
.PAS → program sursă PASCAL;
.CBL → program sursă COBOL;
.C → program sursă C;
.TXT → fişier text;
16. Formate grafice:
.JPG
.GIF
.PNG
Algoritmi în programare 52
Asupra unui fişier se pot executa diverse operaţii de prelucrare, numite şi de gestiune,
care se împart în operaţii la nivel de fişier şi la nivel de articol.
Operaţiile la nivel de articol se referă la accesul la entităţile de date ale fişierului (arti-
cole, blocuri, linii sau cîmpuri) în vederea prelucrării lor. Privite sub aspectul semnificaţiei
pentru utilizator, aceste operaţii se referă la: înscrierea iniţială a entităţilor pe purtătorul tehnic
(populare), actualizarea fişierului prin includerea de noi entităţi (adăugare), modificarea valo-
rilor unor cîmpuri din anumite entităţi (modificare), eliminarea entităţilor care nu mai sînt ne-
cesare (ştergere), regăsirea entităţilor în vederea satisfacerii unor cerinţe de informare (con-
sultare). În programele C, operaţiile de I/E sînt realizate cu ajutorul unei mulţimi de funcţii
specializate pentru căutare, scriere, citire etc.
Trebuie făcută remarca, deosebit de importantă, că din punct de vedere fizic fişierul se
reprezintă în suportul extern ca o succesiune de octeţi. Această succesiune poate fi tra-
tată logic ca un fişier de un tip sau altul. Este sarcina programatorului să asigure „su-
prapunerea” corectă a fişierului logic peste cel fizic. Din acest punct de vedere se poate spune
că prin fişier logic se înţelege, prioritar, un mod de prelucrare şi mai puţin un mod de memo-
rare.
Din punct de vedere al tipurilor de date, în C există un singur tip de fişiere: flux de octeţi
(înşiruire de octeţi, fără nici un fel de organizare sau semnificaţie). Organizarea acestui flux de
octeţi este secvenţială.
Accesul la fişiere se poate face secvenţial sau direct (cu excepţia fişierelor standard, la
care accesul este numai secvenţial). În bibliotecile limbajului există funcţii predefinite pentru
prelucrarea fişierelor. Funcţiile de prelucrare la nivel superior a fişierelor tratează fluxul de
octeţi acordîndu-i o semnificaţie oarecare. Putem spune că din punct de vedere al prelucrării,
la acest nivel, ne putem referi la fişiere text şi fişiere binare.
Există fişiere standard, care sînt gestionate automat de sistem, dar asupra cărora se
poate interveni şi în mod explicit. Acestea sînt:
fişierul standard de intrare (stdin);
fişierul standard de ieşire (stdout);
fierul standard pentru scrierea mesajelor de eroare (stderr);
fişierul standard asociat portului serial (stdaux);
fişierul standard asociat imprimantei cuplate la portul paralel (stdprn).
Crearea şi asignarea unui fişier nou se realizează prin apelul funcţiei creat, care are
următorul prototip:
int creat(const char* numef, int protecţie);
Funcţia returnează manipulatorul fişierului nou creat; numef este un pointer spre un şir
de caractere care defineşte specificatorul de fişier, iar protecţie defineşte modul de protecţie a
fişierului creat (protecţia este dependentă de sistemul de operare). În biblioteca stat.h sînt de-
finite următoarele valori pentru parametrul protecţie: S_IREAD (citire), S_IWRITE (scriere),
S_IEXEC (execuţie). Aceste valori pot fi combinate folosind operatorul | (sau logic pe biţi).
Algoritmi în programare 54
Funcţia creat poate fi apelată şi pentru un fişier existent. Efectul produs este ştergerea fişieru-
lui existent şi crearea unuia gol, cu acelaşi nume; conţinutul fişierului existent se pierde. În
caz de eroare se returnează valoarea –1 şi se setează variabila globală errno, care defineşte ti-
pul erorii. Valorile obişnuite pentru errno sînt EBADF (manipulator eronat, nu a fost găsit fi-
şierul) sau EACCES (fişierul nu poate fi accesat).
Deschiderea unui fişier existent se realizează prin apelul funcţiei open, care are urmă-
torul prototip:
Funcţia returnează manipulatorul fişierului; numef este pointer spre un şir de caractere
care defineşte specificatorul de fişier; acces este modul de acces la fişier; constantele care
descriu modurile de acces la fişier sînt descrise în fcntl.h. Cele mai importante sînt:
O_RDONLY – fişierul va fi accesat numai pentru citire; O_WRONLY – fişierul va fi accesat
numai pentru scriere; O_RDWR – fişierul va fi accesat atît pentru citire cît şi pentru scriere;
O_CREAT: fişierul va fi creat ca nou. Aceste moduri pot fi combinate folosind operatorul |.
Mod este folosit numai dacă parametrul acces conţine şi valoarea O_CREAT, caz în care in-
dică modul de protecţie a acestuia: S_IWRITE – se permite scrierea în fişier; S_IREAD – se
permite citirea din fişier; S_IREAD|S_IWRITE – se permite atît scrierea cît şi citirea din fişi-
er.
Citirea dintr-un fişier se realizează prin apelul funcţiei read, care are următorul antet:
Funcţia returnează numărul de octeţi citiţi din fişier; nf este manipulatorul de fişier
(alocat la crearea sau deschiderea fişierului), zonat este un pointer spre zona tampon în care se
face citirea (aceasta este definită de programator), iar n este dimensiunea zonei receptoare
(numărul maxim de octeţi care se citesc). Numărul maxim de octeţi care pot fi citiţi este
65534 (deoarece 65535 – 0xFFF – se reprezintă intern la fel ca -1, indicatorul de eroare). În
cazul citirii sfîrşitului de fişier se va returna valoarea 0 (0 octeţi citiţi), iar la eroare se retur-
nează -1 (tipul erorii depinde de sistemul de operare). Fişierul standard de intrare (stdin) are
descriptorul de fişier 0.
Scrierea într-un fişier se realizează prin apelul funcţiei write, care are următorul proto-
tip:
Închiderea unui fişier se realizează prin apelul funcţiei close, care are următorul proto-
tip:
int close(int nf);
Poziţionarea într-un fişier se realizează prin apelul funcţiei lseek, care are următorul
prototip:
Funcţia returnează poziţia faţă de începutul fişierului, în număr de octeţi; nf este mani-
pulatorul de fişier; offset este un parametru de tip long (numărul de octeţi peste care se va de-
plasa pointerul în fişier), iar start este poziţia faţă de care se face deplasarea: 0 (începutul fişi-
erului), 1 (poziţia curentă în fişier) sau 2 (sfîrşitul fişierului). La eroare returnează valoarea
-1L.
Exemple
17. Apelul
vb=lseek(nf, 0l, 2);
realizează poziţionarea la sfîrşitul fişierului (în continuare se poate scrie în fişier folosind
write).
18. Apelul
vb=lseek(nf, 0l, 0);
realizează poziţionarea la începutul fişierului.
Ştergerea unui fişier existent se realizează prin apelul funcţiei unlink, care are următo-
rul prototip:
Funcţia returnează 0 (ştergere cu succes) sau -1 (eroare); numef este un pointer spre un
şir de caractere care defineşte specificatorul de fişier. În caz de eroare se setează variabila
errno cu valoarea ENOENT (fişierul nu a fost găsit) sau EACCES (accesul interzis pentru
această operaţie, de exemplu pentru fişiere read only). Pentru a putea şterge un fişier read
only trebuie întîi schimbate drepturile de acces la fişier, folosind funcţia chmod:
unde cale este specificatorul de fişier iar mod noile permisiuni. Permisiunile sînt aceleaşi ca la
funcţia open. Rezultatul întors de chmod are aceeaşi semnificaţie ca şi unlink.
Exemplu
19.
#include <sys\stat.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
int main(void)
{ int handle;
char msg[] = "Acesta este un test";
char ch;
/* citire cite unui caracter din fisier pina la sfirsitul sau si afisare */
do {read(handle, &ch, 1);
printf("%c", ch);}
while (!eof(handle));
close(handle);
return 0;}
Bibliotecile limbajului conţin şi alte funcţii pentru prelucrarea fişierelor la nivel inferior,
inclusiv variante ale funcţiilor anterioare, apărute odată cu dezvoltarea sistemelor de operare.
FILE* f;
Parametrul nume_extern constituie specificatorul de fişier iar, mod este un şir de caractere ca-
re specifică modul de deschidere a fişierului. Asignarea se realizează prin expresie de atribui-
re de tipul:
nume_intern=fopen(sir_nume_extern,sir_mod);
Exemplu
19.
FILE* f;
f = fopen("PROD.DAT","r");
La opţiunile de mai sus se poate adăuga b pentru fişiere binare sau t pentru fişiere text.
Dacă nu este prezentă nici litera b nici litera t, modul considerat depinde de valoarea variabi-
lei _fmode: dacă valoarea este O_BINARY, se consideră fişier binar; dacă valoarea este
O_TEXT, se consideră fişier text. De obicei implicită este valoarea O_TEXT.
Modurile uzuale pentru deschiderea fişierelor sînt prezentate în tabelul 3.2.
Închiderea fişierelor se realizează prin apelul funcţiei fclose, care are următorul prototip:
Funcţia închide fişierul primit ca parametru şi returnează valoarea 0 în caz de succes sau -1, în
caz de eroare. Înainte de închiderea fişierului, sînt golite toate zonele tampon asociate lui. Zo-
nele tampon alocate automat de sistem sînt eliberate.
Algoritmi în programare 58
Executarea funcţiei are ca efect poziţionarea la începutul fişierului f (care era deschis anteri-
or), resetarea indicatorului de sfîrşit de fişier şi a indicatorilor de eroare (se înscrie valoarea
0). După apelul lui rewind poate urma o operaţie de scriere sau citire din fişier.
Macro-ul furnizează valoarea indicatorului de sfîrşit de fişier asociat lui f. Valoarea acestui
indicator este setată la fiecare operaţie de citire din fişierul respectiv. Valoarea întoarsă este 0
(fals) dacă indicatorul are valoarea sfîrşit de fişier şi diferit de zero (adevărat) în caz contrar.
Apelul lui feof trebuie să fie precedat de apelul unei funcţii de citire din fişier. După
atingerea sfîrşitului de fişier, toate încercările de citire vor eşua, pînă la apelul funcţiei
rewind sau închiderea şi redeschiderea fişierului .
Golirea explicită a zonei tampon a unui fişier se realizează prin apelul funcţiei fflush,
care are următorul prototip:
Dacă fişierul f are asociată o zonă tampon de ieşire, funcţia scrie în fişier toate informaţiile din
acesta, la poziţia curentă. Dacă fişierul are asociată o zonă tampon de intrare, funcţia îl goleş-
te. În caz de succes returnează valoarea zero, iar în caz de eroare valoarea EOF (definită în
stdio.h).
Exemplu
21. Înainte de a citi un şir de caractere de la tastatură, zona tampon trebuie golită pen-
tru a preveni citirea unui şir vid (datorită unei perechi CR/LF rămase în zona tampon de la o
citire anterioară a unei valori numerice). Ştergerea se realizează prin apelul:
fflush(stdin);
Aflarea poziţiei curente în fişier se realizează prin apelul uneia din funcţiile fgetpos
sau ftell:
După apel, la adresa poziţie se află poziţia pointerului de citire/scriere din fişierul f, ca număr
relativ al octetului curent. Primul octet are numărul 0. Valoarea returnată poate fi folosită pen-
tru poziţionare cu funcţia fsetpos. În caz de succes funcţia întoarce valoarea 0, iar în caz de
eroare o valoare nenulă şi setează variabila errno la valoarea EBADF sau EINVAL.
returnează poziţia în fişierul f a pointerului de citire/scriere în caz de succes sau -1L în caz
contrar. Dacă fişierul este binar, poziţia este dată în număr de octeţi faţă de începutul fişieru-
lui. Valoarea poate fi folosită pentru poziţionare cu funcţia fseek.
* Material didactic pentru ID * 59
unde deplasare reprezintă numărul de octeţi cu care se deplasează pointerul în fişierul f, iar
origine reprezintă poziţia faţă de care se deplasează pointerul. Parametrul origine poate fi:
SEEK_SET (0) – poziţionare faţă de începutul fişierului; SEEK_CUR (1) – poziţionare faţă
de poziţia curentă; SEEK_END (2) – poziţionare faţă de sfîrşitul fişierului. Funcţia returnează
valoarea 0 în caz de succes (şi uneori şi în caz de eşec). Se semnalează eroare prin returnarea
unei valori nenule numai în cazul în care f nu este deschis.
Poziţionarea absolută se face cu funcţia:
Redenumirea sau mutarea unui fişier existent se poate realiza prin apelul funcţiei
rename, care are următorul prototip:
unde n_vechi reprezintă vechiul nume al fişierului, iar n_nou reprezintă numele nou.
Dacă numele vechi conţine numele discului (de exemplu C:), numele nou trebuie să conţină
acelaşi nume de disc. Dacă numele vechi conţine o cale, numele nou nu este obligat să conţină
aceeaşi cale. Folosind o altă cale se obţine mutarea fişierului pe disc. Folosind aceeaşi cale
(sau nefolosind calea) se obţine redenumirea fişierului. Nu sînt permise wildcard-uri (?, *) în
cele două nume.
În caz de succes se întoarce valoarea 0. În caz de eroare se întoarce -1 şi errno primeş-
te una din valorile: ENOENT – nu există fişierul, EACCES – nu există permisiunea pentru
operaţie sau ENOTSAM – dispozitiv diferit (mutarea se poate face doar pe acelaşi dispozitiv).
Ştergerea unui fişier existent se poate realiza prin apelul funcţiei unlink, prezentată an-
terior, sau remove, care are următorul prototip:
Citirea dintr-un fişier binar se realizează prin apelul funcţiei fread, care are următorul
prototip:
size_t fread(void* ptr,size_t dim,size_t n,FILE* f);
Funcţia citeşte din fişierul f, de la poziţia curentă, un număr de n entităţi, fiecare de dimensiu-
ne dim, şi le depune, în ordinea citirii, la adresa ptr. fread returnează numărul de entităţi citite.
Algoritmi în programare 60
În total se citesc, în caz de succes, n*dim octeţi. În caz de eroare sau cînd se întîlneşte sfîrşitul
de fişier, funcţia returnează o valoare negativă sau 0; size_t este definit în mai multe header-e
(între care stdio.h) şi este un tip de dată folosit pentru a exprima dimensiunea obiectelor din
memorie. Este compatibil cu tipul unsigned.
Exemplu
22.
struct complex {int x,y} articol;
FILE * f_complex;
if(f_complex=fopen("NR_COMPL.DAT", "rb")
fread(&articol,sizeof(articol),1,f_complex);
else printf("Fisierul nu poate fi deschis");
În exemplul anterior se deschide un fişier binar din care se citeşte un articol de tip struct com-
plex care se depune în variabila articol.
Scrierea într-un fişier binar se poate realiza prin apelul funcţiei fwrite, care are urmă-
torul prototip:
Funcţia scrie în fişierul f, începînd cu poziţia curentă, un număr de n entităţi contigue, fiecare
de dimensiune dim, aflate în memorie la adresa ptr; fwrite returnează numărul entităţilor scri-
se cu succes. În caz de eroare se returnează o valoare negativă.
Exemplu
23.
struct complex {int x,y} articol;
FILE *pf;
pf=fopen("NR_COMPL.DAT","wb");
fwrite(& articol,sizeof (articol),1,pf);
Exemplul anterior creează un fişier binar nou în care scrie o secvenţă de octeţi conţinînd re-
prezentarea binară a unei date de tip struct complex.
Exemplu
24. Să se scrie funcţia care calculează numărul de articole dintr-un fişier binar,
cunoscînd lungimea în octeţi a unui articol. Funcţia are ca parametri fişierul şi lungimea în oc-
teţi a unui articol. Prin numele funcţiei se întoarce numărul de articole din fişier.
int nrart(FILE *f, int l)
{ long p;
int n;
p=ftell(f);
fseek(f,0,2);
n=ftell(f)/l;
fseek(f,0,p);
return n;
}
Funcţia fgetc şi macrodefiniţia getc returnează următorul caracter din fişierul f (după
ce îl converteşte la reprezentarea de tip întreg fără semn). Dacă s-a ajuns la sfîrşitul fişierului,
funcţia va întoarce EOF (valoarea -1). Tot EOF va întoarce şi dacă sînt probleme la citirea din
fişier.
Funcţia fputc şi macrodefiniţia putc scriu caracterul c în fişierul f. În caz de eroare se
returnează valoarea c, altfel se returnează EOF.
Funcţia ungetc pune caracterul c în zona tampon de citire asociată fişierului f. La ur-
mătoarea citire cu fread sau getc acesta va fi primul octet/caracter citit. Un al doilea apel al
funcţiei ungetc, fără să fie citit primul caracter pus în flux, îl va înlocui pe acesta. Apelarea
funcţiilor fflush, fseek, fsetpos, sau rewind şterge aceste caractere din flux. În caz de succes,
ungetc returnează caracterul c, iar în caz de eroare returnează EOF.
Funcţia fgets citeşte un şir de caractere din fişierul f şi îl depune la adresa s. Transferul
se încheie atunci cînd s-au citit n-1 caractere sau s-a întîlnit caracterul newline. La terminarea
transferului, se adaugă la sfîrşitul şirului din memorie caracterul nul ‘\0’. Dacă citirea s-a ter-
minat prin întîlnirea caracterului newline, acesta va fi transferat în memorie, caracterul nul fi-
ind adăugat după el (spre deosebire de gets, care nu îl reţine). La întîlnirea sfîrşitului de fişier
(fără a fi transferat vreun caracter) sau în caz de eroare fgets returnează NULL. În caz de suc-
ces returnează adresa şirului citit (aceeaşi cu cea primită în parametrul s).
Funcţia fputs scrie în fişierul f caracterele şirului aflat la adresa s. Terminatorul de şir
(‘\0’) nu este scris şi nici nu se adaugă caracterul newline (spre deosebire de puts). În caz de
succes fputs returnează ultimul caracter scris. În caz de eroare returnează EOF.
Cele două funcţii lucrează identic cu printf şi scanf. Singura diferenţă constă în fişierul
în/din care se transferă datele. Dacă printf şi scanf lucrează cu fişierele standard stdin şi
stdoud, pentru fprintf şi fscanf este necesară precizarea explicită a fişierului cu care se lucrea-
ză, prin parametrul f.
Aceste funcţii lucrează identic cu printf şi scanf, diferenţa constînd în entitatea din/în
care se transferă datele. În locul fişierelor standard, acest funcţii folosesc o zonă de memorie
de tip şir de caractere, a cărei adresă este furnizată în parametrul s. Şirul de la adresa s poate fi
obţinut prin transfer fără format dintr-un fişier text (pentru sscanf) sau poate urma să fie scris
într-un fişier text prin funcţia fputs.
Algoritmi în programare 62
Tratarea erorilor
Pentru tratarea erorilor se folosesc următoarele funcţii:
void clearerr (FILE* f);
Funcţia resetează indicatorii de eroare şi indicatorul de sfîrşit de fişier pentru fişierul f (se în-
scrie valoarea 0). Odată ce indicatorii de eroare au fost setaţi la o valoare diferită de 0, opera-
ţiile de intrare/ieşire vor semnala eroare pînă la apelul lui clearerr sau rewind.
int ferror (FILE* nume_intern);
ferror este o macrodefiniţie care returnează codul de eroare al ultimei operaţii de intrare/ieşire
asupra fişierului nume_intern (0 dacă nu s-a produs eroare).
Exemplu
25. Exemplul următor creează un fişier nou din care încearcă să citească date
#include <stdio.h>
int main(void)
{ FILE *f;
/* deschide fisierul pentru scriere*/
f=fopen("test.ttt","w");
Exemplu
26. Să se scrie un program care calculează şi afişează valoarea unei funcţii introduse
de la tastatură într-un punct dat. Funcţia se introduce ca şir de caractere şi poate conţine ape-
luri de funcţii standard C. Programul creează un fişier sursă C (în care este scrisă forma func-
ţiei, ca subprogram C), apoi compilează şi execută un alt program, care va include subpro-
gramul creat. Descrierea funcţiei introduse de la tastatură trebuie să conţină maxim 200 carac-
tere. (Exemplul a fost scris în mediul Borlandc 3.11.)
a) Fişierul 382_a.cpp conţine programul care realizează citirea formei funcţiei, compilarea şi
execuţia programului care calculează valoarea funcţiei.
#include<stdlib.h>
#include<stdio.h>
#include<conio.h>
#include<string.h>
#include<process.h>
void main()
{ char s1[213]="return(";
char s2[]="double f(double x)\r\n\{\r\n";
FILE *f; int n,i,j;
f=fopen("functie.cpp","w");
fputs(s2,f);
printf("functia f(x)="); gets(&s1[7]);
strncat(s1,");\r\n}",6);
fputs(s1,f);
fclose(f);
system("bcc –Id:\borlandc\include -Ld:\borlandc\lib 382_b.cpp>>
tmp.txt");
execl("382_b ",NULL);
}
* Material didactic pentru ID * 63
b) Fişierul 382_b conţine programul care face citeşte punctul x, calculează valoarea func-
ţiei în acest punct şi o afişează.
#include<stdio.h>
#include<conio.h>
#include<math.h>
#include"functie.cpp"
void main()
{ double x;
printf("x=");scanf("%lf",&x);
printf("f(%7.2lf)=%7.2lf",x,f(x));
getch();
}
Teste de autoevaluare
4. Ce este un fişier?
5. Care sînt operaţiile de bază pentru prelucrarea unui fişier de date?
6. Scrieţi un program care preia un text de la tastatură şi îl scrie într-un fişier nou creat în di-
rectorul curent.
Rezumat
În cadrul acestei unităţi de învăţare au fost studiate următoarele aspecte ale programă-
rii calculatoarelor cu privire la articole şi fişiere de date:
tipul de dată internă articol;
tipuri de fişiere;
utilizarea articolelor interne pentru prelucrarea fişierelor externe;
operaţii generale de prelucrare a fişierelor de date.
După încheierea studierii acestei unităţi de învăţare, studenţii sînt au cunoştinţele şi
abilităţile necesare lucrului cu structuri de date externe, indispensabile în cadrul aplicaţiilor de
informatică economică.
Cuprins
După studierea acestei unităţi de învăţare, studenţii vor avea cunoştinţe teoretice şi
abilităţi practice necesare prelucrării fişierelor de date. Vor fi asimilate abilităţile
necesare prelucrării fişierelor organizate secvenţial, relativ şi indexat. Studenţii vor
putea să efectueze toate operaţiile de prelucrare a fişierelor de date: creare, consultare, actua-
lizare.
De cele mai multe, ori o aplicaţie necesită existenţa mai multor fişiere active simultan,
cu rol diferit (de intrare, de ieşire, de intrare/ieşire). Indiferent de numărul fişierelor utilizate,
în marea majoritate a algoritmilor, logica prelucrării este coordonată, la un moment dat, de un
singur fişier, obligatoriu de intrare, parcurs secvenţial, numit fişier conducător (sau director).
Fişierul conducător are proprietatea că articolele lui pot fi citite logic independent de prelucra-
rea altor fişiere. Altfel spus, un fişier nu este conducător dacă prelucrarea articolelor sale este
dependentă de existenţa (de citirea) articolului altui fişier. Accesul la datele memorate în fişie-
rul conducător se realizează la nivel de articol. De aceea, algoritmii de prelucrare, indiferent
de operaţia de gestiune, necesită utilizarea unei structuri repetitive pentru parcurgerea (parţia-
lă sau integrală) a fişierului respectiv.
Algoritmii de prelucrare cu fişier conducător pot fi reprezentaţi prin schema logică ge-
neralizată, concepută modularizat, redată în figura 4.1.
Modulul SFÎRŞIT se execută o singură dată, după prelucrarea ultimului articol al fişie-
rului conducător şi include următoarele grupe de operaţii: operaţii finale standard,
corespunzînd închiderii fişierelor implicate în prelucrare; operaţii finale specifice, care depind
de natura problemei şi includ, de regulă: afişarea variabilelor de total, a statisticilor privind
operaţiile de gestiune executate, închiderea situaţiilor de ieşire etc.
Algoritmi în programare 66
Fig. 4.2. Detectarea sfîrşitului de fişier (orice Fig. 4.3. Detectarea sfîrşitului de fişier (fişier
fişier) binar)
p=ftell(f);
fseek(f,0,SEEK_END);
l=ftell(f);
nr=l/sizeof(tip_articol);
fseek(f,p,SEEK_SET);
unde: variabila p, de tip long reţine poziţia curentă în fişier; f este fişierul a cărui lungime tre-
buie calculată; variabila l reţine poziţia curentă (în număr de octeţi faţă de începutul fişierului,
deci lungimea fişierului măsurată în octeţi); variabila nr va primi ca valoare numărul de arti-
cole din fişie; tip_articol este tipul articolelor din fişier (din punctul de vedere al utilizatoru-
lui). Împărţirea se face exact, deoarece fişierul conţine un număr întreg de articole – utilizarea
acestei secvenţe asupra unui fişier care conţine articole de alt tip (sau are conţinut de altă na-
tură) va duce la rezultate incorecte.
<citire articol>
while(!feof(f))
{ <prelucrare articol citit>
<citire articol>
}
Exemplu
1. Crearea şi consultarea unui fişier text care memorează elemente întregi, folosind
funcţia feof pentru gestionarea sfîrşitului de fişier. La crearea fişierului, fişier conducător este
fişierul standard de intrare. La afişare, conducător este fişierul f.
Algoritmi în programare 68
#include<stdio.h>
#include<conio.h>
void main()
{ FILE *f;
int x; long dim;
clrscr(); f=fopen("numere.dat","w+");
scanf("%d",&x);
while(!feof(stdin))
{ fprintf(f,"%d\n",x);
scanf("%d",&x);
}
fseek(f,0,SEEK_SET);
fscanf(f,"%d",&x);
while(!feof(f))
{ printf("%d\t",x);
fscanf(f,"%d",&x);
}
fclose(f);
getch();}
Exemplu
2. Acelaşi exemplu, folosind fişier binar:
#include<stdio.h>
#include<conio.h>
void main()
{ FILE *f;
int x,g; long dim;
clrscr(); f=fopen("numere.dat","wb+");
scanf("%d",&x);
while(!feof(stdin))
{ fwrite(&x,sizeof(x),1,f);
scanf("%d",&x);
}
fseek(f,0,SEEK_SET);
fread(&x,sizeof(x),1,f);
while(!feof(f))
{ printf("%d\t",x);
fread(&x,sizeof(x),1,f);
}
fclose(f);
c=getch();}
Fişierele utilizate într-o aplicaţie informatică au rol diferit în procesul prelucrării, în
funcţie de scopul lor: de intrare, de ieşire, de intrare/ieşire, temporare, de tip listă etc. Aceste
caracteristici conduc la algoritmi specifici fiecărei operaţii de gestiune în parte (creare, popu-
lare, consultare şi actualizare), fiind însă variante derivate din schema generală a unui algo-
ritm de prelucrare cu fişier conducător. Deoarece aplicaţiile informatice din domeniul econo-
mic, social, administrativ etc. utilizează, cu predilecţie, fişiere cu articole de aceeaşi structură
(sau un număr mic de structuri diferite), alegînd limbajul C, se poate aprecia că cele mai per-
formante sînt fişierele binare, ale căror articole sînt date declarate ca structuri (folosind tipul
de date struct).
Această alegere este motivată din următoarele puncte de vedere:
descrierea articolelor este apropiată atît descrierii naturale a structurii unei entităţi din
lumea reală (formată din cîmpuri cu nume, lungime, reprezentare internă proprie, semni-
ficaţie şi factor de repetabilitate diferite), cît şi descrierii din alte limbaje;
există posibilitatea de a descrie explicit mai multe structuri pentru articolele aceluiaşi fi-
şier (articole cu structură variabilă);
operaţiile de acces la înregistrări se realizează cu viteză mare, datorită lipsei conversiilor
la transferul între memoria principală şi memoria externă.
* Material didactic pentru ID * 69
Fişierele cu conţinut de tip text sînt recomandate a fi utilizate ca fişiere de ieşire, pen-
tru realizarea de liste, situaţii finale, rapoarte etc., fiind rezidente pe disc, în general, pînă la
listarea lor la imprimantă. Fişierele cu conţinut de tip text pot constitui şi sursa de creare a fi-
şierelor binare, dacă acestea au fost populate cu date, fie prin editoare de texte, fie prin alte
limbaje (Cobol, Fortran, Basic, Pascal), sisteme de gestiune a bazelor de date (DBase, Fox-
Pro, Oracle etc.), constituind unicul mijloc de compatibilitate directă.
Teste de autoevaluare
1. Explicaţi cum se detectează sfîrşitul de fişier în limbajul C şi ce influenţă are acest
aspect asupra algoritmilor de prelucrare a fişierelor.
Popularea fişierelor se realizează prin preluarea datelor fie din alte fişiere primare (cu
conţinut binar sau de tip text), fie de la tastatură (popularea interactivă). În ultimul caz, cel mai
des întîlnit în practică, fişierul conducător corespunde mulţimii datelor introduse de la tastatură.
Articolele sînt preluate cîmp cu cîmp, neexistînd posibilitatea citirii unei variabile de tip articol
şi, în plus, introducerea unei date este adesea însoţită de proceduri de validare specifice, cu
reintroducerea ei în cazul unei erori.
Schema logică a algoritmului de prelucrare este similară celei din figura 4.2., cu
următoarele particularităţi:
- modulul ÎNCEPUT are ca ultime operaţii, afişarea numelui primului cîmp din articol şi
citirea valorii sale;
- modulul PRELUCRARE începe cu citirea următorului cîmp, urmată de citirea
celorlalte cîmpuri (eventual cu validările stabilite) şi se termină cu afişarea numelui primului
cîmp şi cu citirea valorii acestuia (pentru articolul următor) (similar operaţiei din modulul
ÎNCEPUT).
unde n este numărul relativ al articolului iar tip_articol este tipul de dată care îi corespunde.
Exemplu
3. Să se creeze cu populare densă un fişier PRODUSE.DAT, cu informaţii despre
producţia cantitativă dintr-un an la o societate comercială. Articolele au următoarea
structură logică:
Cod produs Denumire produs Preţ mediu Cantităţi lunare
1 2 ... 12
Articolele sînt introduse de la terminal, cîmp cu cîmp. Terminarea introducerii datelor este
marcată standard, prin introducerea caracterului CTRL-Z.
#include <stdio.h>
//---INCEPUT---
printf("\n\nNumele fisierului: ");
gets(nume_fisier);
if(!(f=fopen(nume_fisier,"wb"))) printf("\n\nNu poate fi creat fisierul cu
numele %s",nume_fisier);
else
{ printf("\nCod produs: ");
scanf("%d",&x.cod);
//---Aici se termina operatiile initiale---
while(!feof(stdin))
{
//---PRELUCRARE ARTICOL---
printf("Denumire produs: ");
fflush(stdin);
gets(x.denumire);
printf("Pret mediu: ");
scanf("%f",&x.pret_mediu);
printf("Cantitate lunara:\n");
for(i=0;i<12;i++)
{ printf(" - luna %d: ",i+1);
scanf("%d",&x.cant[i]);
}
fwrite(&x,sizeof(PRODUS),1,f);
//---Aici se incheie prelucrarea articolului---
printf("\nCod produs: ");
scanf("%d",&x.cod);
}
//---SFIRSIT---
fclose(f);
}
}
* Material didactic pentru ID * 71
cîmpul COD indică numărul relativ al articolului în fişier şi nu va fi memorat (nu va face
parte din declaraţia tipului PRODUS), fiind redundant;
scrierea articolului va fi precedată de apelul funcţiei
fseek(f,codt*sizeof(PRODUS),SEEK_SET);
Exemplu
4. Să se afişeze pe ecran conţinutul fişierului creat la exemplul anterior.
#include <stdio.h>
typedef struct { int cod;
char denumire[20];
float pret_mediu;
int cant[12];
} PRODUS;
void main()
{ FILE* f;
PRODUS x;
char nume_fisier[20];
int i;
//---INCEPUT---
printf("\n\nNumele fisierului: ");
gets(nume_fisier);
if(!(f=fopen(nume_fisier,"rb"))) printf("\n\nNu poate fi deschis fisierul
cu numele %s",nume_fisier);
else
{ fread(&x,sizeof(PRODUS),1,f);
//---Aici se termina operatiile initiale---
while(!feof(f))
{
//---PRELUCRARE ARTICOL---
printf("\n\nCod produs:\t\t%d",x.cod);
printf("\nDenumire produs:\t%s",x.denumire);
printf("\nPret mediu:\t\t %7.2f",x.pret_mediu);
printf("\nCantitati lunare:\t");
for(i=0;i<12;i++)
printf("%3d ",x.cant[i]);
//---Aici se incheie prelucrarea articolului---
fread(&x,sizeof(PRODUS),1,f);
}
//---SFIRSIT---
fclose(f);
}
}
Algoritmi în programare 72
Teste de autoevaluare
2. Folosind fişierul creat la exemplul 3, scrieţi un program care determină luna (lunile)
din an în care producţia valorică a societăţii a înregistrat valoarea maximă.
Exemplu
5. Obţinerea unei situaţii cu mai multe grade de total. Pentru aceasta se stabilesc
cîmpuri asociate gradelor de total, numite caracteristici de grupare sau caracteristici de
control. O caracteristică de control este un cîmp al articolului din fişierul de date, care are
aceeaşi valoare pentru mai multe înregistrări. Astfel, articolele care au valoare comună pentru o
caracteristică de grupare se pot ordona pe submulţimi, formînd o grupă de control. Fişierul
poate constitui, în ansamblul său, caracteristica de grupare de cel mai înalt nivel, pentru care se
poate calcula totalul general.
Numărul maxim de grade de total este superior cu unu numărului de caracteristici de
control stabilite. Între caracteristicile de grupare se stabileşte o relaţie de ordine ierarhică. Pentru
prelucrarea fişierului, cu utilizare minimă de memorie, articolele trebuie sortate după carac-
teristicile de control. Acest tip de prelucrare intră în categoria consultărilor secvenţiale integrale
şi urmează algoritmul de principiu din figurile 4.2 sau 4.3. Prelucrarea unui fişier sortat după
criteriile enunţate, presupune existenţa unor operaţii standard, executate la schimbarea valorii
fiecărei caracteristici de control stabilite:
operaţii iniţiale ale unei grupe de control prin care se iniţializează variabila de total
specifică grupei; se salvează valoarea caracteristicii primului articol din grupă; alte operaţii
iniţiale specifice grupei;
operaţii finale ale unei grupe de control prin care se afişează totalul calculat pentru
caracteristica ce se schimbă; se cumulează totalul grupei curente la totalul grupei ierarhic
superioare; alte operaţii finale specifice grupei;
condiţia de prelucrare a unei grupe de control conţine, pe lîngă condiţia specifică, toate
celelalte condiţii din amonte.
Raportul final este listat la imprimantă, fie direct, ca fişier de ieşire, fie creat pe suport
magnetic, ca fişier text, în vederea imprimării ulterioare. Structura unei pagini a raportului şi
controlul trecerii la o nouă pagină trebuie asigurate de programator.
În figura 4.6 se prezintă structura de principiu a unui program de obţinere a unui raport
final, cu control după două caracteristici şi trei grade de total, unde cîmp_1 şi cîmp_2 sînt
caracteristicile de control (cîmpuri din articol), v1 şi v2 sînt variabile de lucru pentru salvarea
caracteristicilor, val_art e valoarea care interesează din fiecare articol (cîmp al articolului sau
valoare calculată pe baza unor cîmpuri ale articolului) iar TOTG, TOT1 şi TOT2 sînt variabile
pentru calculul gradelor de total. Analog, se poate extinde pentru oricîte caracteristici şi grade
de total.
* Material didactic pentru ID * 73
START
Operaţii iniţiale
generale
TOTG=0
Citeşte
articol
!feof(f) Da
Operaţii iniţiale
Grupa 1
TOT1=0
v1=cîmp_1
! feof(f) şi
Da
v1==cîmp_1
Operaţii iniţiale
Nu Grupa 2
Operaţii finale
Geupa 1 TOT2=0
v2=cîmp_2
TOTG+=TOT1
! feof(f) şi
v1==cîmp_1 şi Da
v2==cîmp_2
Prelucrare articol
TOT1+=TOT2
Citeşte
STOP articol
Nu
Teste de autoevaluare
3. Fie un fişier binar organizat relativ, cu articole avînd următoarea structură:
Note
Nr. matricol Nume şi prenume Facultate An de studiu Grupa
1 2 … 20
int char[30] char[10] int int int int int
fseek(f,nr*sizeof(tip_articol), SEEK_SET);
fread(&art,sizeof(tip_articol), 1, f);
Exemplu
6. Secvenţa generală pentru prelucrarea în acces direct.
{
// citire nume fisier extern
f=fopen(nume_fisier, "rb");
// calculare numar de articole din fisier
printf("\nNr. relativ: ");
scanf("%d",&r); //citirea numarului relativ al articolului
while(!feof(stdin))
{ if(r>=nr_art) printf("\n Articol inexistent !");
else
{ fseek(f,r*sizeof(tip_articol),SEEK_SET);
fread(&art,sizeof(tip_articol),1,f);
// ------------------------
//PRELUCRARE ARTICOL
//------------------------
}
printf("\nNr. Relativ (sau CTRL-Z): ");
scanf("%d",&r);
}
fclose(f);
}
Consultarea în acces mixt utilizează o combinaţie între accesul direct şi cel secvenţial,
în vederea prelucrării unui grup de articole, memorate contiguu în fişier şi selectabile printr-o
condiţie. Pentru fişierele binare, metoda poate fi aplicată dacă se doreşte selectarea articolelor
dintre două limite ale numerelor relative (limita inferioară - li şi limita superioară - ls).
Algoritmul trebuie să verifice relaţia 0≤li≤ls≤dimensiune fişier, după care parcurgerea fişierului
poate fi realizată prin orice tip de structură repetitivă.
Exemplu
7. Secvenţa generală pentru prelucrarea în acces mixt.
{
// citire nume fisier extern
f=fopen(nume_fisier, "rb");
// calculare numar articole din fisier
printf("\nLimita inferioara: ");
scanf("%d",&li); // citirea nr. relativ al primului articol
// din secventa
printf("\nLimita superioara: ");
scanf("%d",&ls); // citirea nr. relativ al ultimului articol
// din secventa
if((0<li)&&(li<=ls)&&(ls<=nr_art))
{ fseek(f,li*sizeof(tip_articol),SEE_SET);
for(i=li;i<=ls;i++)
{ fread(&art,sizeof(tip_articol),1,f);
* Material didactic pentru ID * 75
// -----------------------
// Prelucrare articol
// -----------------------
}
}
else printf(" Nu este indeplinita conditia de limite");
fclose(f)
}
Exemplu
8. Secvenţa generală pentru adăugarea datelor la sfîrşitul fişierului.
Inserarea unor articole. Se aplică în cazul în care articolele sînt scrise în fişier în
ordinea crescătoare (descrescătoare) a valorilor unui anumit cîmp. În acest caz, noul articol va fi
inserat între două articole, astfel:
se caută (cu un algoritm secvenţial, binar etc.) poziţia k în care trebuie inserat noul articol;
se copiază, glisînd cu o poziţie spre dreapta, toate articolele de la sfîrşitul fişierului pînă la
articolul cu numărul relativ k;
se scrie în acces direct noul articol, în poziţia k.
Exemplu
9. Secvenţa generală pentru inserarea articolelor pe anumite poziţii.
Modificarea valorii unor cîmpuri din articol se realizează în mai multe etape:
se citeşte articolul care se modifică (fseek şi fread);
se modifică (în zona articol din memoria principală) cîmpurile cu valorile dorite, introduse,
în general, de la tastatură;
se repoziţionează pe articolul respectiv cu
fseek(f,ftell(f)-sizeof(tip_articol),SEEK_SET);
se scrie articolul modificat, cu funcţia fwrite.
Din punct de vedere tehnic, orice cîmp poate fi modificat. Din punct de vedere logic
însă, nu e nevoie sau e chiar imposibil să modificăm unele cîmpuri. În aplicaţii se
prevede posibilitatea modificării numai pentru acele cîmpuri a căror modificare este
normală din punctul de vedere al problemei care se rezolvă.
Exemplu
10. Secvenţa generală pentru modificarea unui articol.
// ------------------------------
// cautare articol de modificat
// ------------------------------ *)
fread(&art,sizeof(tip_articol),1,f);
O altă variantă se poate realiza prin folosirea unei machete de ecran în care se afişează
valorile actuale ale fiecărui cîmp de modificat, se poziţionează succesiv cursorul la începutul
fiecărui cîmp, cu două răspunsuri posibile ale utilizatorului: <ENTER>, caz în care se menţine
actuala valoare, respectiv o tastă diferită de <ENTER>, reprezentînd primul caracter al noii valori.
Teste de autoevaluare
4. Fie fişierul creat la exemplul 3. Scrieţi un program care înregistrează în fişier producţia
realizată de societatea comercială într-o anumită lună a anului, pentru un anumit produs.
Programul trebuie să ofere posibilitatea repetării operaţiei pentru mai multe produse şi diverse
luni. Datele se preiau de la tastatură iar terminarea prelucrării este marcată standard.
Extinderea articolelor logice cu un indicator de stare (un octet), ajungîndu-se la forma din
figura 4.7.
IS Articol propriu-zis
Fig. 4.7. Structura articolului care include indicatorul de stare
Indicatorul de stare (notat IS) poate lua una din cele două valori posibile (de exemplu 0
pentru articol inactiv – inexistent sau şters, 1 pentru articol prezent). Cu această convenţie,
operaţiile de acces la articole se realizează în următoarele condiţii: scrierea în fişier este permisă
numai pentru articolele cu IS=0; citirea din fişier este permisă numai pentru articolele cu IS=1.
Preformarea presupune deschiderea fişierului ca nou (crearea unui fişier nou) şi scrierea
unui număr de articole (la limită, zero) cu IS=0. Includerea operaţiei de preformare conduce la
dispariţia distincţiei dintre populare şi adăugare. Datorită faptului că fişierul se deschide ca
existent, orice operaţie de scriere a unui nou articol se tratează ca adăugare. Într-un sistem de
programe, deschiderea cu modul wb a unui fişier se realizează o singură dată, în procedura de
preformare.
Teste de autoevaluare
5. Fie structura logică de articol de la tema de autoevaluare 3. Considerînd numărul
matricol cheie relativă, scrieţi un program care creează şi populează un fişier
organizat relativ cu articole referitoare la studenţii universităţii.
Datorită restricţiei impuse pentru numărul de articole din fişier, acest model de gestiune
a articolelor este ineficient pentru multe probleme. Se pot concepe algoritmi prin care în tabela
de ocupare în fişier fiecărui articol îi corespunde un bit, în loc de un octet. În acest fel numărul
maxim de articole ce pot fi adăugate în fişier se măreşte de 8 ori.
Cu verificarea existenţei de articole libere, caz în care se adaugă noul articol în prima
poziţie găsită disponibilă (IS=0), eventual la sfîrşit (extindere), dacă nu mai există articole libere
în interior. Această variantă presupune existenţa unei soluţii de gestiune a articolelor libere (în
urma preformării sau a ştergerii logice).
Dintre soluţiile posibile pot fi menţionate:
În această soluţie, nal este numărul articolelor libere şi al[i], cu i=1..nal, reprezintă
poziţiile relative ale articolelor libere. Soluţia prezintă avantajul timpului redus de căutare şi de
atribuire a unei poziţii pentru noul articol. Numărul de articole libere ce pot fi gestionate în acest
mod este limitat de descrierea articolelor principale ale fişierului. De exemplu, dacă articolul
principal are 128 de octeţi, această soluţie permite gestionarea a 63 de articole libere (dacă se
impune pentru articolul zero aceeaşi lungime ca şi pentru celelalte articole; pentru primul articol
Algoritmi în programare 80
se poate accepta şi o dimensiune diferită – mai mare – dar nu cu mult mai mare şi oricum este o
dimensiune stabilită de la început, care nu mai poate fi mărită la nevoie). La ştergerea logică a
unui articol se realizează incrementarea valorii lui nal, iar al[nal] primeşte ca valoare numărul
relativ al articolului şters.
Folosirea articolului zero ca început al unei liste simple (sau dublu) înlănţuite a
articolelor libere, într-o structură de principiu de forma celei din figura 4.9.
pal ual
0 au 0 au ... 0 au
...
În această soluţie, articolul zero punctează pe primul (pal) şi pe ultimul (ual) articol
liber, iar fiecare articol punctează pe următorul (au).
Numărul articolelor libere care pot fi gestionate în acest mod este oarecare. La
adăugarea unui nou articol, se verifică dacă există articole libere (pal<>0) şi dacă există, se
atribuie primul articol din listă articolului de adăugat, actualizîndu-se componenta articolului
zero. La ştergerea unui articol, trebuie asigurată includerea sa în lista articolelor libere, operaţia
fiind posibilă la oricare din capetele listei.
Căutarea secvenţială a primului articol liber, fără organizarea unei gestiuni a acestora.
Deşi mai costisitoare ca timp de căutare, aceasta este soluţia cea mai simplă sub aspectul
programării, eliminînd necesitatea unei structuri distincte a articolului zero şi operaţiile
legate de întreţinerea colecţiei sau listei articolelor libere.
În concluzie, este de preferat ultima variantă atunci cînd timpul de căutare nu este
prohibitiv.
Fără verificarea existenţei de articole libere, caz în care articolul este adăugat direct la
sfîrşit (extindere). Această variantă este avantajoasă cînd are loc introducerea de la început a
majorităţii articolelor. Ea poate fi asociată cu preformarea cu zero articole, fiecare sesiune
de adăugare de noi articole fiind realizată prin extinderea fişierului.
Adăugarea în acces direct presupune o codificare anterioară (preluarea numărului relativ din
nomenclatorul editat după fiecare creare/ adăugare secvenţială) şi se realizează identic cu
operaţia de scriere directă prezentată în §4.3.1.
Indicatorul de stare (IS) are rol identic cu cel prezentat în §4.3.1. Cheie este un cîmp în
care se memorează valoarea cheii articolului existent logic în fişierul de date, al cărui număr
relativ corespunzător este memorat în cîmpul nr. Articolele tabelei de indexuri sînt, în orice
moment, sortate crescător după valorile cîmpului cheie. Articolele din fişierul de date sînt
memorate aleator. O parte dintre acestea nu-şi regăsesc corespondent în tabela de indexuri, fiind
considerate şterse. Orice operaţie de acces la articolele fişierului de date se realizează numai
prin intermediul tabelei, gestionată automat de funcţiile unei biblioteci specializate şi care este
netransparentă utilizatorului (bibliotecă utilizator, nu face parte din limbaj).
Crearea în acces secvenţial presupune furnizarea articolelor sortate strict crescător după
valorile cîmpului ales drept cheie. Articolele sînt scrise cu ajutorul funcţiei de scriere în acces
secvenţial. Eroarea de cheie invalidă poate apărea la tentativa de scriere a unui articol a cărui
cheie este mai mică sau egală decît ultima înscrisă în fişierul de date.
Crearea în acces direct se realizează cu funcţia de scriere în acces direct, articolele fiind
furnizate în orice ordine. Eroarea de cheie invalidă apare la tentativa de scriere a unui articol a
cărui cheie este egală cu una din cele prezente în fişier.
Consultarea în acces mixt permite selectarea unui grup de articole, memorate logic
contiguu în fişier, selecţie realizată pe baza valorilor cheii primului şi ultimului articol din
grupul dorit. Accesul mixt presupune poziţionarea pe primul articol prin citirea în acces direct
(sau poziţionare şi citire în acces secvenţial), urmată de exploatarea în acces secvenţial, pînă la
găsirea cheii ultimului articol dorit, sau pînă la sfîrşitul fişierului.
Ştergerea în acces secvenţial elimină articolul curent din fişierul de date. În general, articolul
trebuie mai întîi identificat printr-o citire (în acces secvenţial sau direct) sau prin poziţionare.
Procedura returnează eroare în cazul tentativei de ştergere după ultimul articol existent.
Algoritmi în programare 82
Ştergerea în acces direct elimină articolul a cărui cheie este precizată. Operaţia nu trebuie
precedată de citirea articolului şi returnează eroare în cazul furnizării unei chei inexistente în
fişier. În aplicaţii, este preferabilă ştergerea în acces secvenţial, deoarece permite (datorită citirii
care o precede), vizualizarea articolului şi luarea unei decizii în condiţii de siguranţă.
Exemplu
11. Exemplul următor descrie funcţii, tipuri de date şi variabile publice pentru
prelucrarea unui fişier organizat indexat. Pentru aceste exemplu, fişierul de date este format din
articole cu următoarea structură:
Exemplul poate fi adaptat pentru orice altă structură, modificînd corespunzător structura
articolului. În acest exemplu, fişierul index va fi o variabilă globală, accesibilă tuturor
subprogramelor. Tipurile definite sînt următoarele:
Pentru implementarea operaţiilor de gestiune specifice unui fişier organizat indexat sînt
necesare următoarele subprograme:
Funcţia primeşte ca parametru numele extern al fişierului de date (nume) şi creează un fişier
nou, tabela de indexuri, cu extensia .idx.
Funcţia primeşte ca parametru numele extern al fişierului de date (nume), şi deschide ca exis-
tentă tabela de indexuri, cu extensia .idx.
void closeindex();
Funcţia pentru citirea în acces secvenţial a unui articol din fişierul de date, cu prototipul
Funcţia are ca parametri numele intern al fişierului de date şi adresa unde se depune articolul
citit, dacă acest lucru este posibil şi returnează
- 1, dacă citirea a fost posibilă;
- 0, în caz contrar.
Citirea unui articol din fişierul de date este realizată prin intermediul tabelei de inde-
xuri, astfel: este citit un articol din tabelă, de la poziţia curentă a pointerului de fişier şi apoi
este citit articolul cu numărul relativ dat de cîmpul nr_rel al articolului citit din fişierul de in-
dexuri. Dacă, în tabela de indexuri, pointerul de fişier indică sfîrşitul de fişier, atunci citirea
nu este posibilă şi funcţia returnează valoarea 0. Prin apelul repetat al funcţiei ReadSec, dacă
tabela de indexuri are poinetrul plasat înaintea primului articol, sînt obţinute articolele din fi-
şierul de date în ordinea strict crescătoare a valorii cheii.
Funcţia pentru citirea în acces direct a unui articol din fişierul de date, cu prototipul
Funcţia are ca parametri numele intern al fişierului de date, adresa unde se depune articolul ci-
tit, dacă acest lucru este posibil, precum şi cheia articolului care va fi citit şi returnează
- 1, dacă citirea a fost posibilă;
- 0, în caz contrar.
Funcţia apelează modulul de căutare binară în tabela de indexuri a cheii Key, SeekKey.
Dacă cheia este găsită, citeşte articolul cu numărul relativ corespunzător articolului din tabela
de indexuri şi returnează valoarea 1, altfel returnează valoarea 0.
Funcţia pentru scrierea în acces secvenţial a unui articol în fişierul de date, cu prototipul
Funcţia are ca parametri numele intern al fişierului de date şi articolul ce va fi scris, dacă acest
lucru este posibil, şi returnează
- 1, dacă scrierea a fost posibilă;
- 0, în caz contrar.
Funcţia adaugă un articol în fişierul de date, concomitent cu extinderea tabelei de in-
dexuri cu o nouă înregistrare, a cărei cheie este mai mare decît cele existente. În cazul în care
cheia este mai mică sau egală cu a ultimului articol din tabelă, este returnată valoarea 0, co-
respunzătoare situaţiei în care scrierea nu este posibilă.
Funcţia pentru scrierea în acces direct a unui articol în fişierul de date, cu prototipul
Funcţia are ca parametri numele intern al fişierului, articolul ce va fi scris, dacă acest lucru es-
te posibil, şi returnează
- 1, dacă scrierea a fost posibilă;
- 0, în caz contrar.
Funcţia adaugă un articol la sfîrşitul fişierului de date. Cheia acestuia, a.cheie, poate
avea orice valoare (care nu există deja în tabela de indexuri). Iniţial, tabela se extinde cu un
nou articol şi apoi este reordonată (prin apelul funcţiei Sort). În cazul în care cheia articolului
de scris este deja prezentă în tabela de indexuri, articolul nu este scris în fişier şi funcţia retur-
nează valoarea 0. Căutarea cheii în tabela de indexuri pentru stabilirea posibilităţii scrierii este
realizată prin apelul funcţiei SeekKey.
Funcţia returnează
- 1, dacă ştergerea a fost posibilă;
- 0, în caz contrar.
Funcţia şterge logic articolul curent din fişierul de date. Ştergerea se realizează fizic în
tabela de indexuri. Iniţial, indicatorul de stare este setat pe 0 şi apoi se elimină articolul din
tabelă, prin apelul funcţiei Sort. Funcţia returnează valoarea 0, corespunzătoare situaţiei de
eroare, dacă pointerul curent al tabelei de indexuri indică marcatorul de sfîrşit de fişier.
Funcţia realizează sortarea articolelor tabelei de indexuri, crescător după cîmpul cheie, pre-
cum şi ştergerea fizică a tuturor articolelor cu indicator de stare 0.
Funcţia realizează căutarea binară în tabela de indexuri, după cîmpul cheie. Dacă arti-
colul cu cheia Key este găsit, funcţia lasă pointerul de fişier pe acel articol (o citire secvenţială
ulterioară determinînd obţinerea articolului corespunzător din fişierul de date).
Exemplu
Textul sursă care implementează toate aceste funcţii C este prezentat în continuare (în
exemplele care urmează, aceste text va fi considerat salvat în fişierul index1.cpp).
#include <stdio.h>
#include <string.h>
void Sort()
{ ART_INDEX a,b;
fisier ind1;
long i,j;
ind1=fopen("temp.idx","wb+");
rewind(ind);
fread(&a,sizeof(a),1,ind);
while(!feof(ind))
{ if(a.is)fwrite(&a,sizeof(a),1,ind1);
fread(&a,sizeof(a),1,ind);
}
fclose(ind);
fseek(ind1,0,SEEK_END);
long n=ftell(ind1)/sizeof(a);
for(i=0;i<n-1;i++)
{ fseek(ind1,i*sizeof(a),SEEK_SET);
fread(&a,sizeof(a),1,ind1);
for(j=i+1;j<n;j++)
{ fseek(ind1,j*sizeof(a),SEEK_SET);
fread(&b,sizeof(a),1,ind1);
if(strcmp(a.cheie,b.cheie)>0)
{ fseek(ind1,i*sizeof(a),SEEK_SET);
fwrite(&b,sizeof(a),1,ind1);
fseek(ind1,j*sizeof(a),SEEK_SET);
fwrite(&a,sizeof(a),1,ind1);
}
}
}
rewind(ind1);
ind=fopen(nume_index,"wb+");
fread(&a,sizeof(a),1,ind1);
while(!feof(ind1))
{ if(a.is)fwrite(&a,sizeof(a),1,ind);
fread(&a,sizeof(a),1,ind1);
}
fclose(ind1);
remove("temp.idx");
}
Algoritmi în programare 86
void close_index()
{ fclose(ind);
}
n=ftell(ind)/sizeof(a1);
if(n>0)
{ fseek(ind,(n-1)*sizeof(a1),SEEK_SET);
fread(&a1,sizeof(a1),1,ind);
if(strcmp(a1.cheie,a.cheie)>0) r=0;
else { ai.is=1;
strcpy(ai.cheie,a.cheie);
fseek(f,0,SEEK_END);
n1=ftell(f)/sizeof(a);
ai.nr_rel=n1;
fseek(ind,0,SEEK_END);
fwrite(&ai,sizeof(ai),1,ind);
fwrite(&a,sizeof(a),1,f);
r=1;
}
}
else r=0;
return r;
}
int DeleteSec()
{ ART_INDEX a1;
long pos=ftell(ind);
fread(&a1,sizeof(a1),1,ind);
if(feof(ind)) r=0;
else { fseek(ind,pos,SEEK_SET);
a1.is=0;
fwrite(&a1,sizeof(a1),1,ind);
Sort();
r=1;
}
return r;
}
Exemplu
12. Scrieţi programul C pentru crearea în acces direct unui fişier binar cu articole
avînd structura:
cheie denumire preţ cantitate
Algoritmi în programare 88
Datele sînt preluate de la tastatură pînă la apăsarea combinaţiei CTRL/Z pentru cîmpul cheie.
Fişierul creat este organizat indexat.
#include "index1.cpp"
#include <conio.h>
void main()
{ ARTICOL a;
char nume[20],nume1[20];
char x[7];
fisier f;
clrscr();
printf(" numele fisierului de date in care adaugati:");
fflush(stdin);
gets(nume);
strcpy(nume1,nume);
strcat(nume1,".dat");
f=fopen(nume1,"rb+");
if(f==NULL)
{ printf("Fisierul va fi creat");
f=fopen(nume1,"wb+");
new_index(nume);
}
else open_index(nume);
printf("\nAdaugarea in acces direct dupa cheie\n");
printf("Introduceti cheia:");
fflush(stdin);
gets(a.cheie);
while(!feof(stdin))
{ printf("Denumire produs:");
fflush(stdin);
gets(a.den);
printf("Pret produs:");
scanf("%f",&a.pu);
printf("Cantitate:");
scanf("%f",&a.cant);
if(WriteKey(f,a))
printf("Articol adaugat");
else printf("Exista articol");
getch();
clrscr();
printf("Introduceti cheia:");
fflush(stdin);
gets(a.cheie);
}
fclose(f);
close_index();
getch();
}
Exemplu
13. Se presupune creat şi populat fişierul de date din exemplul anterior. Scrieţi pro-
gramul C pentru ştergerea acelor articole ale căror chei sînt introduse de la tastatură. Încheie-
rea introducerii datelor este marcată standard.
#include "index1.cpp"
#include <conio.h>
void main()
{ ARTICOL a;
char nume[20],nume1[20];
char Key[7];
fisier f;
char r;
int i;
clrscr();
* Material didactic pentru ID * 89
Exemplu
14. Scrieţi programul C pentru afişarea informaţiilor memorate în fişierul creat mai
sus. Articolele sînt afişate în ordine crescătoare a cîmpului cheie.
#include "index1.cpp"
#include <conio.h>
void main()
{ ARTICOL a;
char nume[20],nume1[20];
char x[7];
fisier f;
clrscr();
printf(" numele fisierului de date care este consultat:");
fflush(stdin);
gets(nume);
strcpy(nume1,nume);
strcat(nume1,".dat");
f=fopen(nume1,"rb+");
if(f==NULL) printf("Fisierul nu exista!!");
else { open_index(nume);
while(ReadSec(f,&a))
{ printf("Cheie:");
puts(a.cheie);
printf("\nDenumire produs:");
puts(a.den);
Algoritmi în programare 90
printf("Pret produs:");
printf("7.2%f\n",a.pu);
printf("Cantitate:");
printf("%8.2f\n\n",a.cant);
getch();
}
fclose(f);
close_index();
getch();
}
}
Dacă pentru sortarea simplă cheia de sortare poate fi însuşi cîmpul din articol, pentru
cea multiplă este necesară o zonă auxiliară de memorie, în care se construieşte cheia de sortare,
în forma canonică.
Sortarea unui fişier se poate realiza cu aducerea lui integrală în memorie (sortare în
memorie) sau cu aducerea în memorie a cîte unui articol (sortare "direct pe disc"). Indiferent de
modul utilizat, sortarea poate fi realizată printr-unul din algoritmii cunoscuţi pentru masivele de
date: sortare prin interschimbare, prin selecţie, prin inserţie etc.
Exemplu
15. Sortarea cu vehicularea întregului articol.
void main()
{ FILE* f;
STUDENT x[250], aux;
int i,j,n;
f=fopen("STUDENT.DAT","rb+");
fseek(f,0,SEEK_END);
n:=ftell(f)/sizeof(STUDENT);
rewind(f);
//sortarea
for(i=0;i<n-1;i++)
for(j=i+1;j<n;j++)
if(x[i].medie>x[j].medie)
{ aux:=x[i]; //interschimbarea articolelor
x[i]:=x[j]; //nu se poate folosi atribuirea mereu
x[y]:=aux;
}
rewind(f);
Exemplu
16. Sortare cu vehicularea cheii şi indexului.
Sortarea numai cu vehicularea indexului este o metodă mai bună decît precedenta,
deoarece micşorează timpul de execuţie, prin eliminarea interschimbării valorilor cheii de
sortare (mai ales cînd aceasta este multiplă). Valorile cheii de sortare şi numărului relativ
corespunzător indexului se memorează în vectori distincţi. Comparaţiile se realizează pentru
valorile cheii, dar interschimbarea se efectuează numai pentru indexuri. Se va crea un alt fişier
fizic.
Exemplu
17. Sortare cu vehicularea indexului.
void main()
{ FILE *f,*g;
float x[250];
int index[250];
int i,j,n;
STUDENT y;
f=fopen("STUDENT.DAT","rb");
fseek(f,0,SEEK_END);
n:=ftell(f)/sizeof(STUDENT);
rewind(f);
g=fopen("STUDENTS.DAT","wb");
// ------------------------------------------
for(i=0;i<n;i++)
{ fread(&y,sizeof(STUDENT),1,f);
x[i]=y.medie;
index[i]=i;
}
// ------------------------------------------
for(i=0;i<n-1;i++)
for(j=i+1;j<n;j++)
if(x[i]>x[j])
{ aux=index[i];
index[i]=index[j];
index[j]=aux
}
* Material didactic pentru ID * 93
// -------------------------------------------
for(i=0;i<n;i++)
{ fseek(f,index[i]*sizeof(STUDENT),SEEK_SET);
fread(&y,sizeof(STUDENT),1,f);
fwrite(&y,sizeof(STUDENT),1,g);
}
fclose(f);
fclose(g)
}
Sortarea pe disc se aplică fişierelor mari, la care este imposibilă aducerea în memoria
principală chiar şi a minimului de informaţii necesare sortării. În acest caz, operaţia se va realiza
"direct" pe mediul magnetic, cu aducerea în memorie doar a două articole (pentru comparaţii) şi
scrierea în acces direct în acelaşi fişier, prin utilizarea numărului relativ al articolelor prelucrate.
Timpul de prelucrare va fi substanţial mai mare decît la metodele de sortare în memorie,
deoarece operaţiile de intrare/ieşire sînt costisitoare din punct de vedere al resursei timp
calculator. Se poate aplica oricare din algoritmii de sortare cunoscuţi, cu menţiunea că indicii i
şi j vor fi utilizaţi pentru controlul numărului relativ al articolelor în fişier.
Exemplu
18. Sortarea prin interschimbare.
do
{ vb=0;
for(i=0;i<nr_art-1;i++)
{ fseek(f,sizeof(STUDENT)*i,SEEK_SET);
fread(&x,sizeof(STUDENT),1,f);
fread(&y,sizeof(STUDENT),1,f);
if(x.medie>y.medie)
{ fseek(f,sizeof(STUDENT)*i,SEEK_SET);
fwrite(&y,sizeof(STUDENT),1,f);
fwrite(&x,sizeof(STUDENT),1,f);
vb=1;
}
while(vb);
Exemplu
19. Sortare prin selecţie.
for(i=0;i<nr_art-1;i++)
{ fseek(f,sizeof(STUDENT)*i,SEEK_SET);
fread(&x,sizeof(STUDENT),1,f);
for(j=i+1;j<nr_art;j++)
{ fseek(f,sizeof(STUDENT)*j,SEEK_SET);
fread(&y,sizeof(STUDENT),1,f);
if(x.medie>y.medie)
{ fseek(f,sizeof(STUDENT)*i,SEEK_SET);
fwrite(&y,sizeof(STUDENT),1,f);
fseek(f,sizeof(STUDENT)*j,SEEK_SET);
fwrite(&x,sizeof(STUDENT),1,f);
}
}
}
Teste de autoevaluare
6. Scrieţi un program care sortează fişierul descris la testul de autoevaluare 3 după
următoarele criterii: facultate, an de studiu, grupă, alfabetic. (Acest test trebuie
rezolvat înainte de a putea rezolva testul 3)
Algoritmi în programare 94
Interclasarea este operaţia prin care, din două sau mai multe mulţimi ordonate, se obţine
o nouă mulţime, ordonată după acelaşi criteriu. Interclasarea fişierelor apare ca necesitate în
aplicaţiile economice, mai ales în faza de post-populare a fişierelor mari de date, create simultan
pe submulţimi de mai mulţi utilizatori şi necesitînd, în final, reunirea acestora într-unul singur.
Condiţia apriori interclasării este ca toate fişierele parţiale să fie sortate după valorile
aceluiaşi cîmp, pe baza căruia se va realiza, prin comparări succesive, operaţia de interclasare.
Cîmpul poartă denumirea de cheie de interclasare. Interclasarea a n fişiere se poate realiza
simplu prin aplicarea de n-1 ori a operaţiei de interclasare a două fişiere (figura 6.12).
...
1 2 3 4 n
Interclasare 1 ………..
Fişier 1
Interclasare 2 ………………………...
Fişier 2
Interclasare 3 …………………………………………...
Fişier 3
...
Interclasare 3 ………………………………………………………………………………………………………….
Fişier
final Fişier n-1
Se obţin astfel n-1 fişiere intermediare (fişier i), din care numai ultimul se păstrează,
celelalte (împreună cu fişierele iniţiale) se şterg, fie în finalul procesului, fie la sfîrşitul fiecărei
etape intermediare (recomandat). Interclasarea a două fişiere este similară operaţiei aplicate
pentru doi vectori. Dimensiunea fişierului rezultat este suma dimensiunilor fişierelor iniţiale.
Exemplu
20. Se prezintă structura principială a unui program pentru interclasarea a două fişiere
binare. Cheile de interclasare se află în cîmpul c aparţinînd articolelor art_1 şi art_2,
corespunzătoare fişierelor de intrare f şi g, considerate populate dens.
{
//---------------------------------
//citire nume externe ale fisierelor
//---------------------------------
f=fopen(nume_fisier_intrare_1, "rb");
g=fopen(nume_fisier_intrare_2, "rb");
h=fopen(nume_fisier_iesire, "wb");
fread(&art_1,sizeof(tip_articol),1,f);
fread(&art_2,sizeof(tip_articol),1,g);
while((!feof(f)&&(!feof(g)))
if(art_1.c>art_2.c)
{ fwrite(&art_1,sizeof(tip_articol),1,h);
* Material didactic pentru ID * 95
fread(&art_1,sizeof(tip_articol),1,f);
}
else
{ fwrite(&art_2,sizeof(tip_articol),1,h);
fread(&art_2,sizeof(tip_articol),1,g);
}
while(!feof(f))
{ fwrite(&art_1,sizeof(tip_articol),1,h);
fread(&art_1,sizeof(tip_articol),1,f);
}
while(!feof(g))
{ fwrite(&art_2,sizeof(tip_articol),1,h);
fread(&art_2,sizeof(tip_articol),1,g);
}
fclose(f);
fclose(g);
fclose(h)
}
Una din aplicaţiile des întîlnite în lucrul cu fişiere este memorarea masivelor de date de
dimensiuni foarte mari, care fac imposibilă aducerea lor integrală în memoria internă. Problema
principală a prelucrării masivelor (vectori, matrice etc.) memorate în fişiere binare, o constituie
determinarea poziţiei unui anumit element de masiv în cadrul fişierului. Indiferent de numărul
de dimensiuni ale masivului şi de modalităţile de memorare a elementelor sale în cadrul
fişierului, legătura între elementul de masiv care se referă şi numărul relativ al articolului care îl
conţine se realizează pe baza funcţiei rang.
În cazul masivelor memorate în fişiere, prelucrarea acestora depinde de unele
caracteristici particulare:
numărul de dimensiuni ale masivului;
ordinea de memorare în fişier (în ordine lexicografică sau invers lexicografică);
modul de memorare (dens sau nedens);
ordinea de parcurgere a masivului.
Exemplu
21. Să se determine media aritmetică a elementelor unui vector foarte mare, memorat
într-un fişier binar.
#include<stdio.h>
void main()
{ FILE* vector;
float element, medie;
long i,n;
vector=fopen("VECTOR.DAT","rb");
fseek(vector,0,SEEK_END);
n=ftell(f)/sizeof(float);
Algoritmi în programare 96
rewind(f);
medie=0;
for(i=0;i<n;i++)
{ fread(&element,sizeof(float),1,vector);
medie+=element;
}
medie/=n;
printf("\nMedia: %7.3f",medie);
fclose(vector);
}
Exemplu
22. Să se afişeze elementul maxim de pe fiecare coloană a unei matrice de dimensiuni m
x n, memorate dens, într-un fişier binar, în ordine lexicografică. Primul articol conţine numărul
de coloane.
Observaţie: primul articol are dimensiune diferită de celelalte: numărul de coloane este
de tip întreg iar elementele matricei sînt reale. Din dimensiunea totală a fişierului, primii
sizeof(int) octeţi sînt ocupaţi de numărul de coloane, restul constituie matricea propriu
zisă. La calcularea poziţiei unui element în matrice trebuie ţinut cont de faptul că matricea nu
începe la începutul fişierului ci după sizeof(int) octeţi.
* Material didactic pentru ID * 97
#include<stdio.h>
void main()
{ FILE *f;
float max, element;
long i,j,r,m,n;
f=fopen("MATRICE.DAT", "rb");
fread(&n,sizeof(int),1,f); //citire numar de coloane
fseek(f,0,SEEK_END);
m=(ftell(f)-sizeof(int))/(sizeof(float)*n);
for(j=0;j<n;j++)
{ //pozitonare pe primul element din coloana j
fseek(f,j*sizeof(float)+sizeof(int),SEEK_SET);
fread(&element,sizeof(float),1,f);
max=element;
for(i=1;i<m;i++)
{ r=i*n+j; //rangul elementului in matrice
fseek(f,r*sizeof(float)+sizeof(int),SEEK_SET);
fread(&element,sizeof(float),1,f);
if(element>max) max=element;
}
printf("\Maximul pe coloana %2d este %7.3f",j,max);
}
fclose(f);
}
Exemplu
23. Să se determine elementul maxim de pe fiecare coloană a unei matrice de dimensi-
uni m x n, memorată nedens într-un fişier binar, în ordine lexicografică. Primele două articole
conţin numărul de coloane efective şi, respectiv, numărul rezervat de coloane.
Rezolvarea este similară cu exemplul anterior, cu următoarele modificări:
din dimensiunea totală a fişierului, primii 2*sizeof(int) octeţi sînt ocupaţi de dimensiunile
matricei (număr de coloane efectiv respectiv rezervat), iar restul constituie matricea
propriu zisă. La calcularea poziţiei unui element în matrice trebuie ţinut cont de faptul că
matricea nu începe la începutul fişierului ci după 2*sizeof(int) octeţi.
La calcularea rangului unui element (şi implicit a poziţiei sale în fişier) se foloseşte
numărul de coloane rezervare, nu numărul de coloane efective.
Teste de autoevaluare
7. Scrieţi un program care determină produsul scalar dintre doi vectori cu elemente de
tip float memoraţi în două fişiere binare.
8. Scrieţi un program care determină produsul dintre un vector şi o matrice, ambele masive fi-
ind memorate în fişiere binare (ambele au elemente de tip float).
9. Scrieţi un program care determină produsul dintre două matrice cu elemente de tip float,
memorate în fişiere binare.
Teme
1. Alegeţi o structură de date care să descrie cît mai bine produsele aflate în vînzare
într-un magazin de dimensiuni mici. Definiţi tipul articol corespunzător în limbajul
C.
2. Folosind structura logică de date de mai sus, scrieţi o aplicaţie care să realizeze toate opera-
ţiile necesare de gestiune a datelor referitoare la produse. Datele vor fi păstrate într-un fişier
binar organizat relativ.
3. Folosind structura logică de la tema 1 şi exemplul 11 de mai sus, scrieţi o aplicaţie care să
realizeze toate operaţiile necesare de gestiune a datelor referitoare la produse. Datele vor fi
păstrate într-un fişier binar organizat indexat.
Algoritmi în programare 98
3. Sînt trei caracteristici de grupare şi patru grade de total. Rezolvarea se face conform sche-
mei logice din figura 4.6, adăugînd încă un nivel de grupare – universitatea e compusă din fa-
cultăţi, o facultate lucrează pe mai mulţi ani de studiu, în cadrul fiecărui an sînt organizate
mai multe grupe.
4. Se cere de fapt realizarea operaţiei de modificare a datelor din fişier. Se caută produsul în
fişier, secvenţial, după codul său şi se modifică valoarea corespunzătoare lunii introduse de la
tastatură.
5. Structura logică a articolului trebuie extinsă prin adăugarea unui cîmp cu rol de indicator de
stare, ajungînd la structura fizică a articolelor din fişier.
Rezumat
În cadrul acestei unităţi de învăţare au fost studiaţi algoritmii pentru efectuarea tuturor
operaţiilor de prelucrare a fişierelor de date: creare, consultare, căutare, modificare,
ştergere. Au fost analizaţi algoritmi de prelucrare a fişierelor care nu necesită actualizare pre-
cum şi a fişierelor care necesită actualizare. Algoritmii studiaţi descriu modul de rezolvare a
prelucrărilor pentru fişierele organizate secvenţial, relativ şi indexat.
De asemenea, au fost studiate metode de organizare a masivelor în fişiere de date şi
algoritmi de prelucrare a lor.
Prin combinarea operaţiilor individuale se pot construi algoritmi complecşi care să re-
zolve toate problemele de gestiune a volumului mare de date aferent aplicaţiilor de informati-
că economică.
Bibliografie