Sunteți pe pagina 1din 99

Academia de Studii Economice din Bucureşti

Facultatea de Cibernetică, Statistică şi Informatică Economică


Catedra de Informatică Economică

Cristian Uscatu Cătălina Cocianu

Cătălin Silvestru

Algoritmi
în programarea
calculatoarelor
Material didactic pentru ID

Acest material are la bază lucrarea


Programarea calculatoarelor. Algoritmi în programare
autori I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C. Uscatu, M. Mircea
publicată la Editura ASE Bucureşti, 2007

Editura ASE
Bucureşti
2010
Algoritmi în programare 2

Copyright © 2011, Cristian Uscatu, Cătălina Cocianu, Cătălin Silvestru

Toate drepturile asupra acestei ediţii sunt rezervate autorului

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:

Prof. univ. dr. Ion Gh. ROŞCA


Prof. univ. dr. Gabriel ZAMFIR

ISBN 978-606-505-465-3
* Material didactic pentru ID * 3

Titlul cursului: Algoritmi în programare


Introducere: Cursul Algoritmi în programare se adresează studenţilor facultăţii de
Cibernetică, Statistică şi Informatică Economică din cadrul Academiei de Studii Economice
din Bucureşti. Conform planului de învăţămînt, cursul se desfăşoară în semestrul 1 al anului 2
de studiu.

Cursul Algoritmi în programare este destinat iniţierii studenţilor în cîteva aspec-


te ale programării calculatoarelor. Limbajul utilizat pentru demonstrarea concep-
telor şi aplicaţiile practice este C standard (ANSI C). Obiectivele principale ale
cursului vizează însuşirea de către studenţi a cunoştinţelor teoretice şi a abilităţilor practice de lu-
cru referitoare la următoarelor elemente din cadrul programării calculatoarelor:
tipurile dinamice de date şi modul de lucru cu date alocate dinamic (masive de date);
analiza, proiectarea, implementarea şi utilizarea a subprogramelor;
structurile de date externe (fişiere) şi abilităţi de lucru cu acestea.

Cursul Algoritmi în programare este structurat în patru unităţi de învăţare, corespun-


zătoare elementelor principale studiate (date dinamice şi subprograme – cîte o unitate de învă-
ţare, fişiere de date – 2 unităţi de învăţare).

În cadrul procesului de instruire pot fi utilizate ca resurse suplimentare materia-


lele puse la dispoziţie de biblioteca facultăţii, atît în cadrul sălii de lectură cît şi
prin serviciul de împrumut.

De asemenea, laboratoarele Catedrei de Informatică Economică pot fi utilizate


pentru studiu individual pe toată durata programului de lucru, atunci cînd nu se
desfăşoară ore (programarea orelor în laboratoare poate fi consultată la secretari-
atul Catedrei). Calculatoarele din laboratoare oferă medii de programare adecvate pentru dez-
voltarea abilităţilor de lucru în limbajul C şi posibilitatea de accesare a bibliotecii virtuale a
instituţiei.

Evaluarea cunoştinţelor se va realiza prin intermediul lucrărilor susţinute pe par-


cursul semestrului astfel:
o probă practică, constînd în rezolvarea a două probleme de programare
din cadrul tematicii cursului;
o lucrare scrisă.
Ambele teste se susţin în timpul orelor aferente temelor de control din calendarul
disciplinei.

Cele două teste contribuie la formarea notei finale astfel:


proba practică constituie 40% din nota finală;
lucrarea scrisă constituie 50% din nota finală;
din oficiu se acordă 10%.
Algoritmi în programare 4

Cuprins

1. Tipuri dinamice de date....................................................................................................... 6


Obiectivele unităţii de învăţare ............................................................................................... 6
1.1. Tipuri de date dinamice............................................................................................... 6
1.2. Declararea şi iniţializarea pointerilor ........................................................................ 7
1.3. Utilizarea pointerilor.................................................................................................... 8
1.3.1. Operaţii cu pointeri................................................................................................ 8
1.3.2. Legătura între pointeri şi masive........................................................................ 10
1.4. Alocarea dinamică a memoriei.................................................................................. 12
1.5. Modificatorul const .................................................................................................... 13
1.6. Tratarea parametrilor din linia de comandă........................................................... 14
Răspunsuri şi comentarii la testele de autoevaluare ........................................................... 14
Rezumat................................................................................................................................... 15
Bibliografia unităţii de învăţare............................................................................................ 15

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

3. Articolul şi fişierul .............................................................................................................. 40


Obiectivele unităţii de învăţare ............................................................................................. 40
3.1. Articol: caracteristici generale şi mod de declarare................................................ 40
3.2. Referirea articolului şi a elementelor componente.................................................. 42
3.3. Articole cu structuri complexe .................................................................................. 45
3.4. Constante de tip articol .............................................................................................. 46
3.5. Fişierul şi articolul ...................................................................................................... 47
3.6. Metode de organizare a fişierelor şi tipuri de acces ................................................ 48
3.7. Structura sistemului de fişiere sub MS-DOS/Windows .......................................... 50
3.8. Operaţii de prelucrare a fişierelor ............................................................................ 52
3.8.1. Nivelul inferior de prelucrare a fişierelor ........................................................ 53
3.8.2. Nivelul superior de prelucrare a fişierelor ....................................................... 56
Răspunsuri şi comentarii la testele de autoevaluare ........................................................... 63
Rezumat................................................................................................................................... 63
Bibliografia unităţii de învăţare ............................................................................................ 63

4. Algoritmi de prelucrare a fişierelor de date..................................................................... 64


Obiectivele unităţii de învăţare ............................................................................................. 64
4.1. Caracteristici generale ale algoritmilor de prelucrare a fişierelor ........................ 64
4.2. Algoritmi de prelucrare a fişierelor binare care nu necesită actualizare.............. 69
4.3. Algoritmi de prelucrare a fişierelor binare care necesită actualizare ................... 77
4.3.1. Codificarea externă prin numere relative ........................................................ 77
4.3.2. Codificarea internă prin numere relative ........................................................ 79
4.3.3. Corespondenţa internă dintre chei şi numere relative.................................... 80
4.4. Sortarea fişierelor binare memorate dens................................................................ 90
4.5. Interclasarea fişierelor binare memorate dens ........................................................ 94
4.6. Prelucrarea masivelor memorate în fişiere binare.................................................. 95
4.6.1. Prelucrarea vectorilor ........................................................................................ 95
4.6.2. Prelucrarea matricelor....................................................................................... 96
Răspunsuri şi comentarii la testele de autoevaluare ........................................................... 98
Bibliografia unităţii de învăţare ............................................................................................ 98
Bibliografie .............................................................................................................................. 98
Algoritmi în programare 6

1. Tipuri dinamice de date

Cuprins

Obiectivele unităţii de învăţare ............................................................................................... 6


1.1. Tipuri de date dinamice............................................................................................... 6
1.2. Declararea şi iniţializarea pointerilor ........................................................................ 7
1.3. Utilizarea pointerilor.................................................................................................... 8
1.3.1. Operaţii cu pointeri................................................................................................ 8
1.3.2. Legătura între pointeri şi masive........................................................................ 10
1.4. Alocarea dinamică a memoriei.................................................................................. 12
1.5. Modificatorul const .................................................................................................... 13
1.6. Tratarea parametrilor din linia de comandă........................................................... 14
Răspunsuri şi comentarii la testele de autoevaluare ........................................................... 14
Rezumat................................................................................................................................... 15
Bibliografia unităţii de învăţare............................................................................................ 15

Obiectivele unităţii de învăţare

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

1.1. Tipuri de date dinamice

Pointerul este un tip de dată predefinit, care are ca valoare adresa unei zone de memo-
rie (figura 1.1).

Memoria internă

Segmen:offset

Zona de memorie indicată de pointer


Pointer
Fig. 1.1. Un pointer este adresa unei alte zone de memorie
* Material didactic pentru ID * 7

Folosirea pointerilor prezintă următoarele avantaje:


• înlocuirea expresiilor cu indici – înmulţirile din formula de calcul al rangului se transfor-
mă în adunări şi deplasări;
• posibilitatea alocării dinamice a memoriei;
• folosirea tipurilor procedurale de date;
• calculul adreselor.

În operaţiile cu pointeri se folosesc următorii operatori specifici:


Operatori Simbol Utilizare
Operator de referenţiere * tip*
Operator de referenţiere & &nume
Operator de dereferenţiere * *nume

* ⇒ defineşte un nou tip de dată (pointer la tip);


& ⇒ extrage adresa unei variabile (creează o referinţă);
* ⇒ accesează conţinutul zonei de memorie indicate de pointer.

Cei doi operatori au efect invers: *&nume Ù nume.

Exemplu
1. *&nume reprezintă valoarea de la adresa variabilei nume (valoarea variabilei nume).

1.2. Declararea şi iniţializarea pointerilor

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.

Iniţializarea pointerilor se poate realiza ca în exemplul precedent sau, ca şi pentru ce-


lelalte variabile, la declarare, astfel:

int a; int* nume=&a;

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;

Se observă folosirea conversiei de tip (typecasting), atunci cînd se lucrează cu pointeri


spre tipul void (fără tip). Chiar dacă un pointer spre tipul void poate primi ca valoare adresa
unei variabile de orice tip, pentru a putea lucra cu ea este necesară gestionarea corectă a tipu-
lui operanzilor.

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?

1.3. Utilizarea pointerilor

1.3.1. Operaţii cu pointeri


Asupra pointerilor se pot efectua operaţii aritmetice. Fie secvenţa:
int *nume,*nume2, c, a, b; nume=&a; nume2=&a;
* Material didactic pentru ID * 9

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

Situaţia iniţială este următoarea: După cele două operaţii:

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

Adunarea/scăderea unui întreg


În general, dacă p este un pointer spre un tip TIP, atunci cînd se adună un întreg n la
pointerul p, rezultatul va fi tot un pointer spre TIP, care are ca valoare adresa memorată în p,
la care se adună de n ori numărul de octeţi necesari pentru a memora o dată de tip TIP, adică
n*sizeof(TIP). Asemănător se execută scăderea unui întreg dintr-un pointer.
nume+n Ù nume primeşte valoarea nume+n*sizeof(int)
nume-n Ù nume primeşte valoarea nume-n*sizeof(int)

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

Operaţiile descrise anterior se folosesc frecvent în lucrul cu masive.

Compararea a doi pointeri


Limbajul C permite compararea a doi pointeri într-o expresie, folosind oricare din ope-
ratorii relaţionali (==, !=, <, >, <=, >=).
Rezultatul expresiei nume op nume2 (unde op este unul din operatorii precizaţi anteri-
or) este adevărat (nenul) sau fals (zero) după cum nume este egal, mai mare sau mai mic decît
nume2. Doi pointeri sînt egali dacă adresele care constituie valorile lor sînt egale. Privind
memoria internă liniar, începînd de la adresa 0 (zero), un pointer p este mai mare decît altul q,
dacă adresa pe care o conţine p este mai îndepărtată de începutul memoriei decît adresa conţi-
nută de q.
Algoritmi în programare 10

Este permisă şi compararea unui pointer cu o valoare constantă. Uzual se foloseşte


comparaţia cu valoarea NULL pentru a verifica dacă pointerul a fost iniţializat (un pointer ne-
iniţializat are valoarea NULL), folosind unul din operatorii == sau !=. Valoarea NULL este
definită în stdio.h astfel: #define NULL 0
De multe ori se preferă comparaţia directă cu zero (nume==0 sau nume!=0). În loc de nu-
me==0 se poate folosi expresia nume. Aceasta se interpretează astfel: dacă nume nu a fost iniţia-
lizat, atunci are valoarea NULL (adică 0), deci expresia este falsă. În caz contrar valoarea ex-
presiei este nenulă, deci adevărată. Asemănător se foloseşte expresia !nume.

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

Diferenţa dintre doi pointeri


Fie secvenţa:
int m[50],* a, * b;
a=&m[i]; b=&m[j];
unde i şi j sînt întregi în intervalul [0..49]. Expresia a-b are valoarea i-j, interpretată ca dis-
tanţă între adresele a şi b, exprimată în zone de memorie de lungime sizeof(int).
Valoarea unei expresii diferenţă se calculează astfel: se face diferenţa între cele două
adrese (în octeţi), apoi se împarte la dimensiunea tipului de dată referită de cei doi pointeri
(tipul int în exemplul de mai sus – vezi figura 1.2). Cei doi pointeri trebuie să refere acelaşi
tip de dată, altfel rezultatul nu are semnificaţie (trebuie să aibă tipuri identice). Operaţia este
utilă în lucrul cu masive.
m i j

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.

1.3.2. Legătura între pointeri şi masive


În limbajul C numele unui masiv este un pointer către tipul de dată al elementele masivului.
* Material didactic pentru ID * 11

Pentru masivele unidimensionale:


int m[50]; Ù m are tipul int*
int* p; Ù p are tipul int*
Diferenţa constă în faptul că zona de memorie către care punctează m este rezervată la
compilare (ceea ce nu se întîmplă în cazul pointerilor declaraţi ca atare). De aceea m nici nu
poate primi valori în timpul execuţiei programului (nu se poate schimba adresa memorată în
m). El memorează adresa primului element din masiv. Referirea unui element m[i] este echi-
valentă cu *(m+i) – conţinutul de la adresa m+i. Limbajul C nu face nici un fel de verificări în
privinţa depăşirii limitelor indicilor masivului, de aceea expresiile m[500] sau m[-7] vor fi
considerate corecte de compilator, existînd riscul unor erori logice. Este sarcina programato-
rului să se asigure că indicii nu vor depăşi limitele.

Pentru masivele bidimensionale:


int m[50][50]; Ù m are semnificaţia următoare: m[i][j] Ù *(*(m+i)+j), re-
prezintă „conţinutul de la adresa j plus conţinutul de la adresa memorată în m plus i”. Aceasta
poate fi interpretată astfel: m este un pointer spre un vector de pointeri, fiecare element al vec-
torului fiind la rîndul lui un pointer spre o linie a matricei (un vector de elemente de tip float).
Figura 1.3. poate fi utilizată pentru a înţelege mai bine cum se accesează elementele
unui masiv bidimensional.

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

Analog pot fi interpretate masivele cu mai multe dimensiuni.


Exemplu
10. Un masiv cu trei dimensiuni float m[10][10][10] poate fi interpretat ca un pointer
spre un vector de pointeri spre matrice;
Un masiv cu n dimensiuni este tratat ca un pointer spre un vector de pointeri către masive cu
n-1 dimensiuni.

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.

1.4. Alocarea dinamică a memoriei


Pentru a memora o dată de un anumit tip în heap este necesar să se declare un pointer
către acel tip de dată, apoi să se rezerve memoria necesară. Pentru a rezerva spaţiu în heap se
foloseşte funcţia standard:
void* malloc(unsigned n);

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:

void free(void* p);

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:

void* calloc(unsigned nr_elem, unsigned dim_elem);

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.

1.5. Modificatorul const

În limbajul C constantele simbolice se declară prin directiva de preprocesare #define. O


altă posibilitate de lucru cu constante este iniţializarea unei variabile cu o valoare şi interzice-
rea modificării valorii acesteia. În acest scop se foloseşte modificatorul const. Sînt permise
următoarele forme de utilizare:

a) tip const nume = valoare; sau


const tip nume = valoare;

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

b) tip const* nume = valoare; sau


const tip* nume = valoare;

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;

c) const tip* nume;

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

1.6. Tratarea parametrilor din linia de comandă

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

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

unde argc conţine numărul de parametri ai programului, incrementat cu 1.

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

Răspunsuri şi comentarii la testele de autoevaluare


3.
int m,n;
float a[10][10];
printf("Nr. linii:\n"; scanf("%d", &m);
printf("Nr. coloane:\n"); scanf("%d", &n);
for(i=0;i<m;i++)
for(j=0;j<n;j++)
{ printf("a(%d,%d)= ",i,j);
scanf("%f", *(a+i)+j );
}
Observaţie: *(a+i)+j este un pointer, care conţine adresa elementului a[i][j]; în
funcţia scanf trebuie transmise ca parametri adresele unde se depun valorile citite; în
exemplul anterior se putea scrie &*(*(a+i)+j), şi, reducînd, rezultă *(a+i)+j.
* Material didactic pentru ID * 15

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.

Bibliografia unităţii de învăţare

1. I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C. Uscatu, M. Mircea - Programarea


calculatoarelor. Algoritmi în programare, Ed. ASE Bucureşti, 2007
2. I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C. Uscatu - Programarea calculatoa-
relor. Ştiinţa învăţării unui limbaj de programare. Teorie şi aplicaţii, Ed. ASE Bucureşti,
2003
3. Liviu Negrescu - Limbajele C şi C++ pentru începători, vol. I, II, Ed. Microinformatica,
Cluj-Napoca, 1994
Algoritmi în programare 16

2. Subprograme

Cuprins

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

Obiectivele unităţii de învăţare

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.

2.1. Construirea şi apelul subprogramelor

Conform teoriei programării, subprogramele sînt clasificate în funcţii, care returnează


un singur rezultat prin „numele” funcţiei şi oricîte prin parametri de ieşire şi proceduri, care
returnează oricîte rezultate, toate prin intermediul parametrilor de ieşire.
Un program C este un ansamblu de funcţii care realizează activităţi bine definite. Exis-
tă o funcţie, numită main(), care este apelată la lansarea în execuţie a programului. Subpro-
gramele C sînt, în mod nativ, funcţii. Pot fi construite subprograme care nu returnează nici un
rezultat prin numele lor, comportîndu-se ca o procedură (conform definiţiei din teorie).
Sistemele C au colecţii de biblioteci care conţin funcţii standard. Textul sursă al unui
program C poate fi partiţionat în mai multe fişiere. Fiecare fişier constă dintr-un set de funcţii
şi declaraţii globale. Fişierele care constituie partiţia pot fi compilate şi, eventual, testate sepa-
rat, dar numai unul va conţine funcţia main().
* Material didactic pentru ID * 17

Funcţiile C sînt formate din antet şi un corp. Antetul are forma:

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:

[tip1 identificator1[,tip2 identificator[,tip3 identificator …]]]

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.

Corpul este o instrucţiune compusă: conţine declaraţiile locale şi instrucţiunile execu-


tabile care implementează algoritmul. Corpul funcţiei se execută pînă la executarea ultimei in-
strucţiuni sau pînă la executarea instrucţiunii return. Forma ei generală este:

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.

Fiind funcţii, subprogramele C se apelează ca operanzi în expresii, prin numele funcţi-


ei urmate de lista parametrilor reali. Expresia care conţine apelul poate la limită să conţină un
singur operand şi chiar să fie o instrucţiune de tip expresie. În aceste cazuri valoarea returnată
de funcţie se pierde, nefiind folosită în nici un fel.

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>

int cmmdc(int a, int b) /*definirea functiei cmmdc*/


{ int r,d=a,i=b;
do {r=d%i;
d=i; i=r;}
while(r<>0);
return i;
}

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

2. Acelaşi exemplu folosind un prototip pentru funcţia cmmdc:

#include <stdio.h>

int cmmdc(int, int); /* prototipul functiei cmmdc*/

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

int cmmdc(int a, int b) /*definirea functiei cmmdc*/


{ int r,d=a,i=b;
do {r=d%i;
d=i; i=r;}
while(r<>0);
return i;
}

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

2.2. Transferul datelor între apelator şi apelat

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

2.2.1. Transferul prin parametri


Principial, transferul parametrilor se poate face prin valoare sau prin adresă. În limba-
jul C este implementat numai transferul prin valoare (valoarea parametrului real este copiată
în stivă iar subprogramul lucrează numai cu această copie). Operaţiile efectuate asupra unui
parametru formal scalar (care nu este masiv) nu modifică, la ieşirea din subprogram, parame-
trul real corespunzător. Dezvoltările ulterioare (C++) au implementat şi transferul prin adresă.
Transferul valorii este însoţit de eventuale conversii de tip realizate pe baza informaţi-
ilor de care dispune compilatorul despre subprogram (tipurile parametrilor). Dacă prototipul
precede apelul subprogramului şi nu există o sublistă variabilă de parametri, conversiile se fac
similar atribuirilor.

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

Apelul se realizează astfel:


float a[30], b[30];
int dimensiune;
………………
printf("Produsul scalar al vectorilor a si b este:%f", ps(a,b,dimensiune));
Algoritmi în programare 20

În a doua variantă de rezolvare, rezultatul se întoarce prin parametru, simulînd transferul prin
adresă:

void ps(float x[], float y[], int n, float *prod)


{ int i;
*prod=0;
for(i=0;i<n;(*prod)+=x[i]*y[i++]);
}

Apelul se realizează astfel:


float a[30],b[30],produs_scalar;
int dimensiune;
ps(a,b,dimensiune,&produs_scalar);
printf("Produsul scalar al vectorilor a si b este:%f", produs_scalar);

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


{ int i;
for(*max=v[0],i=1;i<n;i++)
if(*max<v[i])
{*nr_ap=1;poz[0]=i; max=v[i];}
else if(*max==v[i])poz[*nr_ap++]=i;
}

Apelul se realizează astfel:


float a[30],el_max;
int dimensiune,nr_aparitii,pozitii[30];
maxim(a,dimensiune,&max,&nr_aparitii,pozitii);

Antetul subprogramului este echivalent cu construcţia

void maxim(float *v, int n, float *max, int *nr_ap, int *poz)

pentru care corpul subprogramului este acelaşi.

7. Să se calculeze produsul a două matrice statice.

void produs(float a[][10],float b[][20], float c[][20],int m, int n,int p)


{ int i,j,k;
for(i=0;i<m;i++)
for(j=0;j<p;j++)
for(c[i][j]=0,k=0;k<n;k++)c[i][j]+=a[i][k]*b[k][j];
}

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

Apelul se realizează astfel:


a) float a[20][30], b[30], *c;
int m,n;
…………………………
c=prod(a,b,m,n);
Cu vectorul c se lucrează în modul obişnuit: elementele se referă prin indexare (c[i], i=0..m ).

b) float a[20][30], b[30];


int m,n;
…………………………
Se lucrează cu „vectorul” prod(a,b,m,n) – elementele sale se referă ca prod(a,b,m,n)[i],
i=0..m.

Observaţie: la fiecare referire de element se apelează şi se execută funcţia, ceea ce


duce la consum mare şi inutil de resurse. Este preferabilă prima variantă.

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

2.2.2. Simularea transmiterii parametrilor prin adresă


Limbajul C permite transmiterea parametrilor numai prin valoare (la apelul subpro-
gramelor se copiază în stivă valoarea parametrului real şi subprogramul lucrează cu această
copie). Subprogramul nu poate modifica valoarea parametrului din apelator.
Dacă parametrul formal este un masiv, el este de fapt un pointer (adresa de început a
masivului). Folosind această proprietate, se pot modifica valorile elementelor masivului, iar
modificările se vor propaga în blocul apelator, deoarece valoarea care se copiază în stivă este
adresa de început a masivului. Masivul rămîne în memoria principală şi poate fi modificat
prin intermediul adresei sale de început. Astfel se poate simula transmiterea parametrilor prin
adresă folosind pointerii. Subprogramul poate modifica valori care să se propage în apelator.
În acest scop se transmite ca parametru un pointer spre variabila cu care trebuie să lu-
creze subprogramul apelat. Subprogramul va folosi explicit acest pointer. Un exemplu în acest
sens este funcţia de citire a datelor de la tastatură. Parametrii acestei funcţii sînt adresele vari-
abilelor ale căror valori trebuie citite (altfel spus: adresele zonelor de memorie unde trebuie
depuse valorile citite de la tastatură).
Algoritmi în programare 22

Exemple
9. Fie un subprogram care calculează suma elementelor unui vector v de lungime n.

void suma(float s, float v[], int n)


{ int i;
for(s=0,i=0;i<n;i++)
s+=v[i];
}

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:

void suma(float* s, float v[], int n)


{ int i;
for(s=0,i=0;i<n;i++) *s+=v[i];
}

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.

void citire(int a, int b, int* x)


{ do
printf("Introduceti numarul: ");
scanf("%d", x);
while((*x<=a)||(*x>=b));
}

2.2.3. Comunicaţia prin variabile globale


Variabilele globale se declară în afara funcţiilor. Ele pot fi referite din orice alte func-
ţii. De aceea, comunicarea valorilor între apelator şi apelat se poate realiza prin intermediul
lor. Variabilele declarate într-o funcţie se numesc locale (din clasa automatic) şi pot fi referite
numai din funcţia respectivă. Domeniul de valabilitate a unei variabile locale este blocul
(funcţia sau instrucţiunea compusă) în care a fost definită.

Exemplu
11.

#include <stdio.h> main()


{ int c;
int a; ……………
{ /* instructiunea compusa r */
float z(char b) int d;
{ int b; ……………
…………… }
} }
* Material didactic pentru ID * 23

Domeniile de valabilitate a referirilor variabilelor declarate sînt următoarele:


b poate fi referit doar în funcţia z;
c poate fi referit doar în funcţia main;
d poate fi referit doar în instrucţiunea compusă r;
a este globală şi poate fi referită de oriunde.

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?

2.3. Pointeri spre funcţii

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

a. Declararea unei variabile de tip procedural (pointer spre funcţie):


tip_rezultat (*nume_var)(lista_parametri_formali);

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

b. Descrierea funcţiei care utilizează parametrul procedural:


void f(…, tip_rezultat (*nume)(lista_parametrilor_formali), …)
{ tip_rezultat x;

x=(*nume)(lista_parametrilor_actuali);

}

unde nume este parametrul formal de tip procedural.

c. Apelul funcţiei cu parametri procedurali:

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.

float suma(float *v, int n)


{ for(int i=0, float s=0; i<n; i++)
s+=v[i];
return(s);
}

float media(float *v, int n)


{ for(int i=0, float m=0; i<n; i++)
m+=v[i];
m/=n;
return(m);
}

float functie( float vec[],int dim,float(* prelucrare)(float*, int))


{
return (*prelucrare)(vec,dim));
}

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

Observaţie: Nu trebuie să se confunde un pointer la o funcţie cu o funcţie care are ca


rezultat un pointer, cu sintaxa de forma
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>

/*prototipul functiei bisectie*/


void bisectie(float,float,float(*f)(float),float,long,int *,float *);

/*prototipul functiei pentru care se aplica metoda bisectiei*/


float fct(float);

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

/*descrierea functiei pentru care se aplica metoda bisectiei*/


float fct(float x)
{ return x*x*x-3*x+14;
}

/*functia ce implementeaza metoda bisectiei*/


void bisectie(float a,float b,float (*f)(float),float eps,long n,
int *cod,float *x)
{ int gata=0;
long c;
for(c=0;(c<n)&&!gata;c++)
{ *x=(a+b)/2;
gata=fabs(*x-a)<eps;
if ((*f)(*x)*(*f)(a)<0)
b=*x;
else a=*x;
}
*cod=gata;
}

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

dimensiunea vectorului de sortat şi numărul de octeţi din reprezentarea tipului elemen-


telor vectorului;
pointerul la o funcţie de comparare, cu argumente de tip void *, care să permită la
apel atît schimbarea de tip, cît şi descrierea efectivă a relaţiei de ordine.

Deoarece tipul elementelor vectorului nu este cunoscut la momentul descrierii proce-


durii de sortare, operaţia de atribuire nu poate fi folosită, ea fiind înlocuită de o funcţie de co-
piere a unui număr prestabilit de octeţi, de la o adresă sursă la una destinaţie. O astfel de func-
ţie există în biblioteca mem.h, sintaxa ei fiind:
void *memmove(void *destinaţie, const void *sursă, unsigned n)

Pentru accesarea adresei elementului de rang i din vector se foloseşte formula


(char *)v+i*nr_octeti.

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>

void sort(void *v, int n, int dim,


int (*compara)(const void * ,const void * ))
{ int i,j;
void *aux;
aux=malloc(dim);
for(i=0;i<n-1;i++)
for(j=i+1;j<n;j++)
if((*compara)((char*)v+dim*i,(char*)v+dim*j))
{ memmove(aux,(char*)v+dim*i,dim);
memmove((char*)v+dim*i,(char*)v+dim*j,dim);
memmove((char*)v+dim*j,aux,dim);
}
free(aux);
}

Exemplu de apel pentru un vector de numere reale:


#include <stdio.h>
#include<conio.h>
#include "exp_tip.cpp"
int compara(const void *a, const void *b)
{ if(*(float *)a>*(float *)b)return 1;
else return 0;
}
void main()
{ float vect[20]; int n,i;
clrscr();
printf("Dimensiunea vectorului:");
scanf("%d",&n);
printf("\nElementele:");
for(i=0;i<n;i++)
scanf("%f",&vect[i]);
sort(vect,n,sizeof(float),compara);
printf("\nElementele sortate:");
for(i=0;i<n;i++)
printf("\n%f",vect[i]);
getch();
}
* Material didactic pentru ID * 27

Exemplu de apel pentru un vector de cuvinte (şiruri de caractere):

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

2.4. Funcţii cu număr variabil de parametri


Bibliotecile limbajului C conţin subprograme standard cu număr variabil de para-
metri. Limbajul C permite definirea funcţiilor utilizator cu număr variabil de parametri, prin
utilizarea unui set de macrodefiniţii, declarate în biblioteca stdarg.h, care permit accesul la
lista de parametri.
Fişierul stdarg.h declară tipul va_list şi funcţiile va_start, va_arg şi va_end, în care:

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:

tip_element va_arg(va_list ptlist, tip_element);


unde tip_element este tipul elementului transferat din listă. După fiecare apel al funcţiei
va_arg, variabila ptlist este modificată astfel încît să indice următorul parametru.
Algoritmi în programare 28

va_end încheie operaţia de extragere a valorilor parametrilor şi trebuie apelată înainte de


revenirea din funcţie. Prototipul funcţiei este:
void va_end(va_list ptlist);

Problema numărului de parametri şi tipurilor lor este tratată de programator.

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>

int cmmdc_var(int,...); // functie cu numar variabil de parametri


int cmmdc(int, int);

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

int cmmdc_var(int nr,...) //cel mai mare divizor comun a nr numere


{ va_list ptlist;

/*initializarea lui ptlist cu adresa de inceput a listei de parametri*/


va_start(ptlist,nr);

//extragerea primului parametru, de tip int


x=va_arg(ptlist,int);

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

16. Să se interclaseze un număr oarecare de vectori.


Spre deosebire de exemplul anterior, în care în lista de parametri a funcţiei cu număr
oarecare de parametri figurau elemente de acelaşi tip (int), acest exemplu ilustrează modul de
transfer şi acces la elemente de tipuri diferite. Funcţiei inter_var i se transmit la apel vectorul
rezultat, iar pentru fiecare vector de interclasat, adresa de început (pointer la tipul double) şi
numărul de elemente (int). Numărul parametrilor din lista variabilă este, în acest, caz 2 x nu-
mărul de vectori de interclasat.
* Material didactic pentru ID * 29

#include<stdarg.h>
#include<stdio.h>
#include<conio.h>

void inter(double *,int,double *,int,double *);


void inter_var(double *,int nr,...);

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

void inter_var(double *z,int nr,...)


{ va_list ptlist;
double *x,*y,x1[100];
int n1,n2;

/*initializarea lui ptlist cu adresa de inceput a listei de parametri*/


va_start(ptlist,nr);
//extragerea primului vector
x=va_arg(ptlist,double *);
//extragerea dimensiunii lui
n1=va_arg(ptlist,int);

for(int j=0;j<n1;j++) x1[j]=x[j];


for(int i=1;i<(int)(nr/2);i++)
{ //extragerea urmatorului vector
y=va_arg(ptlist,double *);
//extragerea numarului sau de elemente
n2=va_arg(ptlist,int);

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

2.5. Calcul recursiv

Recursivitatea este tehnica de programare în care un subprogram se autoapelează.


Limbajul C face parte din clasa limbajelor de programare care admit scrierea de funcţii recur-
sive.
În continuare sînt prezentate cîteva exemple simple de subprograme C prin intermedi-
ul cărora sînt calculate recursiv valorile n! , C nk , f o g o f , unde n , k ∈ N şi f, g funcţii, f , g :
R → R. De asemenea, este ilustrată maniera în care sînt efectuate apelurile recursive şi trata-
rea condiţiilor de terminare.

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

Soluţia recursivă a evaluării C nk este:


long comb(unsigned n, unsigned k)
{ if (k>n) return 0;
if ((k==0)||(k=n)) return1;
return comb(n-1,k)+comb(n-1,k-1);
}

Funcţiile C recursive pentru calculul n! , C nk , unde n , k ∈ N realizează apeluri recursive


directe. Schema unui apel recursiv poate fi descrisă astfel: se verifică dacă este îndeplinită cel
puţin una din condiţiile terminale; dacă este îndeplinită o condiţie terminală, atunci calculul este
încheiat şi controlul este returnat unităţii apelante, în caz contrar este iniţiat calculul pentru noile
valori ale parametrilor, calcul care presupune unul sau mai multe apeluri recursive.

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.

De exemplu, în cazul apelului fact(3), secvenţa de apeluri recursive este: fact(2),


fact(1), fact(0). În continuare execuţia determină fact(0)=1, fact(1)=1*fact(0)=1,
fact(2)=2*fact(1)=2, fact(3)=3*fact(2)=6.
Evoluţia determinată de apelul fact(3) în stivă este ilustrată în figurile 2.1.a şi 2.1.b,
unde (○) reprezintă adresa de revenire în punctul de unde a fost efectuat apelul fact(3).
3 2 1 0

Fact=3*Fact(2)
Fact=1*Fact(0) Fact=1
Fact=2*Fact(1)

2 1
3

(o) Fact=2*Fact(1) Fact=1*Fact(0)


Fact=3*Fact(2)
2
3
(o)
Fact=2*Fact(1)
Fact=3*Fact(2)

(o)
Fact=3*Fact(2)

(o)

Fig. 2.1.a Evoluţia în stivă pînă la verificarea


condiţiei de terminare n=0
Algoritmi în programare 32

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

2.6. Exemple de aplicaţii cu subprograme recursive


Exemplu
20. Să se scrie o funcţie C care citeşte o secvenţă oarecare de cuvinte a1, a2, ….., an
terminată cu simbolul # şi afişează anan-1…a1. Pentru rezolvarea problemei se utilizează func-
ţia recursivă Scrie.
void Scrie()
{ char cuvant[100];
scanf(“%s“,&cuvant);
if (strcmp(cuvant,“#“))
{ Scrie;
printf( “%s\n“,cuvant);
}
}
* Material didactic pentru ID * 33

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.

Notînd cu P(n,a,c) problema transferului celor n discuri de pe tija a pe tija c, pentru


rezolvarea ei putem raţiona în modul următor. Dacă s-a rezolvat problema P(n-1,a,b), atunci
discul de diametru maxim care se află încă pe tija a este deplasat pe tija c şi în continuare se
rezolvă problema P(n-1,b,c). Soluţia recursivă este prezentată în funcţia Hanoi.
Algoritmi în programare 34

Exemplu: presupunînd că discurile sînt numerotate în ordinea crescătoare a diametrelor cu


etichetele 1, 2, 3, o soluţie a problemei pentru n=3 poate fi descrisă astfel.
Tija a Tija b Tija c Mutarea efectuată
1 a⇒c
2
3
2 1 a⇒b
3
3 2 1 c⇒b
3 1 a⇒c
2
1 3 b⇒a
2
1 2 3 b⇒c
1 2 a⇒c
3
1
2
3
#include <stdio.h>
#include <conio.h>

void Hanoi(unsigned n,unsigned a, unsigned b,unsigned c)


{
if(n>0)
{ Hanoi(n-1,a,c,b);
printf("Transfer disc de pe tija %u pe tija %u\n",a,b);
Hanoi(n-1,c,b,a); }
}

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.

void insera(float *v,int *n,float x)


{ for(int i=0;(i<*n)&&(x>v[i]);i++);
for(int j=*n;j>=i+1;j--)v[j]=v[j-1];
v[i]=x;(*n)++;
}

void inssort(float *v,int n)


{ if(n)
{ inssort(v,n-1);int m=n-1;
insera(v,&m,v[n-1]);
}
}
* Material didactic pentru ID * 35

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

Curba Hilbert obţinută este:

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:

Recursia pentru obţinerea curbelor Sierpinski


poate fi descrisă astfel.
S: A B C D
A: A B ⇒ D A
B: B C ⇓ A B
C: C D ⇐ B C
D: D A ⇑ C D

unde săgeţile duble indică segmente de lun-


gime 2h.
Următorul program desenează curbele Sierpinski S4 (exemplul a fost scris în mediul
Borland C 3.11)

#include <stdio.h> setbkcolor(15);


#include <graphics.h> setcolor(8);
#include <stdlib.h> h=h0/4;
#include <conio.h> x0=2*h;
y0=3*h;
const n=4; do{ i++;
const h0=412; x0-=h;h/=2;y0+=h;
int i=0; x=x0;y=y0; moveto(x,y);
int h; A(i);x+=h;y-=h;lineto(x,y);
int x,y,x0,y0,gm; B(i);x-=h;y-=h;lineto(x,y);
int gd=DETECT; C(i);x-=h;y+=h;lineto(x,y);
D(i);x+=h;y+=h;lineto(x,y);}
void A(int); while(i!=n);
void B(int); getch();
void D(int); closegraph();
void C(int); }

void main() void A(int i)


{ clrscr(); { if (i>0)
initgraph(&gd,&gm,"d:\bc\bgi"); { A(i-1);x+=h;y-=h;
* Material didactic pentru ID * 37

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)

Rezultatul execuţiei programului este prezentat în următoarea figură.

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.

Răspunsuri şi comentarii la testele de autoevaluare


1. Pe lîngă rezultatele (oricîte) care pot fi întoarse prin parametrii, subprogramele de
tip funcţie permit şi întoarcerea unui rezultat de tip simplu prin numele lor, ceea ce
ofere posibilitatea realizării apelului ca operand într-o expresie, care va folosi rezultatul apelu-
lui pentru evaluare.
Algoritmi în programare 38

2. În limbajul C se pot scrie numai subprograme de tip funcţie.

3.
#include<stdio.h>
#include<conio.h>
#include<alloc.h>

float** inmultire(float **a,float **b,int n)


{ int i,j,k; float **c;
c=(float **)malloc(n*sizeof(float *));
for(i=0;i<n;i++)
*(c+i)=(float *)malloc(n*sizeof(float));
for(i=0;i<n;i++)
for(j=0;j<n;j++)
for(k=0,c[i][j]=0;k<n;c[i][j]+=a[i][k]*b[k++][j]);
return c;
}

float** putere(float **a,int p,int n)


{ float **c,**ap;int l,m,i;
ap=(float **)malloc(n*sizeof(float *));
for(i=0;i<n;i++)
*(ap+i)=(float *)malloc(n*sizeof(float));
for(l=0;l<n;l++)
for(m=0;m<n;ap[l][m]=a[l][m],m++);
for(i=0;i<p-1;i++)
{ c=inmultire(a,ap,n);
for(l=0;l<n;l++)
for(m=0;m<n;ap[l][m]=c[l][m],m++);
}
return ap;
}

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

int cauta_binar(float *v,int li,int ls,float k)


{ if(li>ls)
return -1;
int mij=(li+ls)/2;
if(v[mij]==k)
return mij;
if(v[mij]>k)
return cauta_binar(v,li,mij-1,k);
return cauta_binar(v,mij+1,ls,k);
}

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.

Bibliografia unităţii de învăţare

1. I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C. Uscatu, M. Mircea - Programarea


calculatoarelor. Algoritmi în programare, Ed. ASE Bucureşti, 2007
2. I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C. Uscatu - Programarea calculatoa-
relor. Ştiinţa învăţării unui limbaj de programare. Teorie şi aplicaţii, Ed. ASE Bucureşti,
2003
3. Liviu Negrescu - Limbajele C şi C++ pentru începători, vol. I, II, Ed. Microinformatica,
Cluj-Napoca, 1994
4. I.Gh. Roşca & colectiv - Bazele elaborării programelor. Exerciţii rezolvate şi propuse, Ed.
ASE Bucureşti, 1998
5. A. Iorgulescu - Metode numerice şi programe Pascal, Ed. Inforec, Bucureşti, 1996 (para-
grafele 2.2.1, 2.2.2, 2.2.3, 4.2.1, 4.2.2)
Algoritmi în programare 40

3. Articolul şi fişierul

Cuprins

Obiectivele unităţii de învăţare ............................................................................................. 40


3.1. Articol: caracteristici generale şi mod de declarare ............................................... 40
3.2. Referirea articolului şi a elementelor componente.................................................. 42
3.3. Articole cu structuri complexe .................................................................................. 45
3.4. Constante de tip articol.............................................................................................. 46
3.5. Fişierul şi articolul...................................................................................................... 47
3.6. Metode de organizare a fişierelor şi tipuri de acces................................................ 48
3.7. Structura sistemului de fişiere sub MS-DOS/Windows.......................................... 50
3.8. Operaţii de prelucrare a fişierelor............................................................................ 52
3.8.1. Nivelul inferior de prelucrare a fişierelor ........................................................ 53
3.8.2. Nivelul superior de prelucrare a fişierelor....................................................... 56
Răspunsuri şi comentarii la testele de autoevaluare ........................................................... 63
Rezumat................................................................................................................................... 63
Bibliografia unităţii de învăţare............................................................................................ 63

Obiectivele unităţii de învăţare

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.

3.1. Articol: caracteristici generale şi mod de declarare

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

Declararea împreună a tipului articol şi a variabilelor de acest tip se realizează con-


form sintaxei:

struct tip_articol{lista_cimpuri} var1, var2, …, varn;

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

DATA dată de grup (articol)

ZI LUNA AN date elementare

a)

PERSOANA dată de grup (articol)

dată de grup (articol)


NUME ADRESA DATA NAŞTERII

date elementare ZI LUNA AN


b)
Fig. 3.1. Exemple de structuri de articole

O variabilă de tip articol poate fi declarată şi ulterior definirii tipului:

struct tip_articol var1;

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

f) typedef struct COMPLEX{float r,i;};


struct COMPLEX a,b[100];
g) typedef struct COMPLEX{float r,i;};
COMPLEX a,b[100];

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;

Variabilele de tip articol se reprezintă intern ca succesiuni de cîmpuri elementare, cu


reprezentarea internă şi lungimea fizică specifice tipurilor lor. Lungimea zonei de memorie
rezervată pentru variabila de tip articol rezultă din însumarea lungimilor cîmpurilor. Aceasta
nu poate depăşi 65520 octeţi (ca orice variabilă de tip structurat). Pentru structura unui articol
îşi dovedeşte utilitatea operatorul sizeof, care asigură determinarea lungimii zonei de me-
morie asociate unei variabile sau unui tip de date.

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

3.2. Referirea articolului şi a elementelor componente

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

Articolul se declară astfel:


struct magazin { int cod_magazin;
float vanzari_lunare[12];
} articol;
Articolul are 50 de octeţi, iar referirea cîmpurilor se realizează astfel:
articol.cod_magazin
articol.vanzari_lunare[i], cu i=0,1,…,11.

Exemplu
7. Se presupune un articol cu structura din figura 3.3.

Cod Număr Materia primă 1 ... Materia primă 30


produs materii Cod Norma de ... Cod Norma de
prime consum consum
întreg întreg întreg real ... întreg real
2 1 2 4 ... 2 4
Fig. 3.3. Structura de articol pentru exemplul 7

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:

struct a { int cod_mat; float norma; };


struct produs { int cod_produs;
unsigned char nr_mat;
struct a materii_prime[30];
} articol ;

Referirea cîmpurilor se realizează astfel:


articol.cod_produs;
articol.nr_mat;
articol.materii_prime[i].cod_mat;
articol.materii_prime[i].norma;

Constantele de tip articol sînt cu tip şi păstrează caracteristicile acestora. În momentul


compilării se rezervă zone de memorie pentru acestea, iar cîmpurile articolelor sînt iniţializate
cu valorile precizate de utilizator. Declararea constantelor presupune definirea anterioară a ti-
pului articol.
Valoarea iniţială trebuie să fie de acelaşi tip cu cîmpul căruia îi corespunde. Cînd arti-
colul conţine la rîndul său alt articol, identificarea cîmpului care se iniţializează se face pe ni-
veluri, folosind perechi corespunzătoare de acolade.

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

//Initializarea articolului din exemplul 1:


struct persoana p={"Popescu Ion", "Bucuresti, Magheru 14", 2, 4, 1960};

//sau cu evidentierea structurii data nasterii:


struct persoana p1={"Popescu Ion", "Bucuresti, Magheru 14", {2, 4, 1960}};
printf("\n%i",p1.datan.an);

//Initializarea articolului din exemplul 2:


struct magazin gigel_srl={200, 1,2,3,4,5,6,7,8,9,10,11,12};

//sau cu evidentierea structurii de masiv:


struct magazin gigel_srl1={200, {1,2,3,4,5,6,7,8,9,10,11,12}};
printf("\n%6.2f",gigel_srl1.vanzari_lunare[10]);

//Initializarea articolului din exemplul 3 (doar primele 4 materii


//prime, restul de 26 vor fi initializate automat cu valori nule:
struct produs z={243,5,{{2420,25.4},{3251,70.21},{1421,8.4},{51,7.2}}};
printf("\n%6.2f",z.materii_prime[2].norma); }

Teste de autoevaluare
1. Clasificaţi tipul de date articol, conform clasificării tipurilor de date.

3.3. Articole cu structuri complexe

În activitatea de programare pot fi întîlnite aplicaţii care reclamă utilizarea articolelor


cu structură variabilă. La iniţializarea cîmpurilor unui astfel de articol, constanta de tip articol
se asociază unei singure structuri, deoarece zona de memorie rezervată pentru articol este uni-
că. Pentru acest tip de articol, limbajul pune la dispoziţia utilizatorilor tipul predefinit reuniu-
ne (union), care se comportă ca şi tipul struct cu o singură diferenţă: la un moment dat al exe-
cuţiei programului, în zona de memorie rezervată articolului nu este memorat decît unul dintre
cîmpurile acestuia.
Declararea tipului reuniune se realizează astfel:

union nume_tip { tip_cimp1 cimp1;


tip_cimp2 cimp2;
................
tip_cimpn cimpn;};

Lungimea zonei de memorie rezervate pentru o variabilă de tip reuniune va fi egală cu


maximul dintre lungimile cîmpurilor componente. Gestiunea conţinutului respectivei zone de
memorie va trebui realizată de către programator.

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

Declararea şi iniţializarea cîmpurilor unui student la zi pentru structura articolului din


figura 3.4 se realizează astfel:

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

//Initializarea cimpurilor unui student la zi:


struct articol a={"Popescu Felix",{4,1,1974} ,1,'Z',{'D',250.5}};
printf("\nData nasterii: %i.%i.%i, Forma de inv.: %c, Val. bursa: %6.2f",
a.datan.zi, a.datan.luna, a.datan.an, a.forma_inv, a.parte_vb.zi.valoare);
}

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)

3.4. Constante de tip articol

Constantele de tip articol pot fi constante cu tip (variabile iniţializate la compilare) şi


constante „obiect”, pentru care în momentul compilării se rezervă zone de memorie, iar
cîmpurile articolelor sînt iniţializate cu valorile precizate de utilizator.
Valoarea iniţială trebuie să fie de acelaşi tip cu cîmpul căruia îi corespunde. Cînd arti-
colul conţine la rîndul său alt articol, identificarea cîmpului care se iniţializează se face pe ni-
veluri, folosind perechi corespunzătoare de acolade.
Constantele cu tip joacă rol de variabile care se iniţializează cu o valoare în faza de
compilare, ele putînd să-şi modifice valoarea pe parcursul execuţiei programului.
tip nume_const = {lista_valori};

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.

3.5. Fişierul şi articolul

Prelucrarea automată a datelor presupune un sistem de organizare a acestora după me-


tode şi procedee specifice. Organizarea datelor este un proces complex care include identifica-
rea, clasificarea şi descrierea proprietăţilor acestora, gruparea lor în colecţii, reprezentarea pe
purtători tehnici, definirea şi realizarea procedurilor de prelucrare etc.
Deoarece datele se memorează, de obicei, pe purtători tehnici de informaţii, dar se pre-
lucrează numai cînd sînt prezente în memoria internă, acestea trebuie organizate atît extern cît
şi intern. În organizarea externă a datelor se identifică două niveluri de abordare, după cum se
are în vedere acest proces din perspectiva utilizatorului sau a purtătorilor fizici externi pe care
se înregistrează datele. Cele două niveluri de abordare, numite logic, respectiv fizic, precum şi
realizarea trecerii de la unul la celălalt, în condiţiile specifice diverselor sisteme de calcul, se
bazează pe o serie de concepte, cum ar fi: fişierul şi articolul, purtătorul tehnic de date, meto-
da de organizare şi modul de acces, operaţiile de prelucrare etc.

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.

3.6. Metode de organizare a fişierelor şi tipuri de acces

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

A1 A2 ... Ak-1 Ak ... An EOF

Fig. 3.5. Principiul de realizare a accesului secvenţial la articole

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

O problemă importantă care se pune la consultarea (citirea) în acces secvenţial este


controlul ajungerii la sfîrşitul fişierului. După citirea ultimei entităţi (articol, bloc, linie sau
cîmp), pointerul indică marcatorul de sfîrşit de fişier - EOF (figura 3.6).
Poziţia pointerului după
citirea ultimului articol

A1 A2 ... Ak-1 Ak ... An EOF

Fig. 3.6. Pointerul după citirea ultimului articol din fişier

În limbajele de programare se regăsesc două modalităţi de sesizare a sfîrşitului de fişier:


a) Sesizarea sfîrşitului de fişier în cadrul operaţiei de citire (limbajele FORTRAN, CO-
BOL, C). Sfîrşitul este sesizat la citirea marcatorului de sfîrşit de fişier. Situaţia din figura 5.2
nu este considerată sfîrşit de fişier. Abia la următoarea citire se întîmplă acest lucru (pointerul
de fişier avansează după marcatorul de sfîrşit de fişier).
b) Sesizarea sfîrşitului de fişier independent de operaţia de citire (limbajele BASIC,
PASCAL). În acest caz, dacă pointerul este pe marcatorul de sfîrşit de fişier (după ultimul ar-
ticol, bloc, linie, cîmp, ca în figura 5.2) se consideră sfîrşit de fişier. Următoarea citire produ-
ce eroare de intrare/ieşire (I/E).
Proiectarea algoritmilor de prelucrare a fişierelor este determinată de modalitatea în
care se sesizează sfîrşitul de fişier.

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, kn) este localizat direct şi conţinutul lui se transferă în
memoria internă.

Fişierele organizate secvenţial, cu articole de lungime variabilă, admit numai accesul


secvenţial. Fişierele organizate secvenţial, cu articole sau blocuri de lungime fixă, admit atît
accesul secvenţial cît şi pe cel relativ. Acest lucru derivă din faptul că accesul relativ este rea-
lizat de sistem printr-o deplasare secvenţială faţă de începutul acestuia, deplasare care este
egală cu valoarea expresiei: număr_relativ × lungime_articol.
Algoritmi în programare 50

Acces direct prin numărul relativ k-1


P(Ak)=k-1

A1 A2 ... Ak-1 Ak ... An EOF

0 1 ... k k-1 ... n-1


Număr relativ
Fig. 3.7. Principiul de realizare a accesului direct prin număr relativ

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.

3.7. Structura sistemului de fişiere sub MS-DOS/Windows

Sistemul de operare MS-DOS utilizează o formă logică arborescentă de grupare a fişi-


erelor de pe discuri în directoare şi subdirectoare. Un director (subdirector) poate conţine fişi-
ere şi/sau alte subdirectoare (figura 5.4). În limbajul curent folosit de practicieni se utilizează
noţiunea de director şi în cazul subdirectoarelor.
Un disc DOS conţine un singur director rădăcină, care la rîndul lui are zero sau mai
multe subdirectoare şi/sau fişiere. Subdirectoarele pot avea oricîte niveluri de imbricare.
Frunzele arborelui sînt, cel mai adesea, fişiere, dar pot fi şi subdirectoare vide. Unitatea de
disc, directorul şi subdirectorul în care se lucrează la un moment dat se numesc curente.

Pentru ca un fişier să fie localizat în cadrul structurii arborescente se foloseşte un iden-


tificator extern (specificator) care are următoarea formă sintactică:

[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

Rădăcina (pe discul C:\)

F1 D1 D2 D3 F2 F3

F4 F2 D4 D5 F5 F6 F7

F8 F9

Fig. 3.8. Exemplu de structură arborescentă de directori

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.

Producătorii de software au impus unele denumiri de extensii, care, deşi opţionale,


oferă posibilitatea simplificării referirii fişierelor în unele comenzi sau aplicaţii.

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

3.8. Operaţii de prelucrare a fişierelor

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 fişier se referă la aspecte ca: înscrierea fişierului în


[sub]directoare, validarea şi interzicerea accesului la fişier (deschidere/închidere), ştergerea
fişierului din [sub]directoare (ştergere) etc. Aceste operaţii se regăsesc, în totalitate, la prelu-
crarea fişierelor pe discuri magnetice. În cazul purtătorilor nereutilizabili, singurele operaţii
care au sens sînt cele de deschidere/închidere a fişierelor.

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.

În concluzie, dacă din punctul de vedere al utilizatorului operaţiile de prelucrare se


descriu relativ simplu, prin apeluri de funcţii, realizarea efectivă a lor de către sistemul de cal-
cul este complexă. În sistemul de operare MS-DOS sînt incluse funcţii de întrerupere care,
prin intermediul BIOS (Basic Input Output System), lansează anumite operaţii cu un echipa-
ment.
Din punct de vedere al reprezentării datelor în suportul extern, se disting fişiere text,
în care toate datele sînt sub formă ASCII (un caracter/octet) şi fişiere binare, în care toate da-
tele sînt memorate în forma identică cu cea din memoria principală (MP).
Strîns legat de lucrul cu cele două tipuri de fişiere este modul în care se face transferul
datelor între memoria principală şi suportul extern: transfer posibil cu conversie (în cazul fişi-
erelor text) şi transfer fără conversie (în cazul fişierelor binare).

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.

Indiferent de limbajul de programare folosit, operaţiile necesare pentru prelucrarea fi-


şierelor sînt:
descrierea fişierului (crearea tabelei care memorează caracteristicile fişierului);
asignarea fişierului intern (numele logic) la unul extern (fizic);
deschiderea fişierului;
operaţii de acces la date („articole”);
închiderea fişierului.
Pentru lucrul cu fişiere trebuie identificate tipurile acestora, metodele de organizare,
modurile de acces şi tipurile de articole acceptate.
* Material didactic pentru ID * 53

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

Fişierele standard pot fi redirectate conform convenţiilor sistemului de operare, cu ex-


cepţia lui stderr care va fi asociat întotdeauna monitorului.
În lucrul cu fişiere (sau la orice apel de sistem), în caz de eroare în timpul unei operaţii
se setează variabila errno, definită în errno.h, stddef.h şi stdlib.h. Valorile posibile sînt defini-
te în stdlib.h.

În limbajul C există două niveluri de abordare a lucrului cu fişiere: nivelul inferior de


prelucrare (fără gestiunea automată a zonelor tampon de intrare/ieşire) şi nivelul superior de
prelucrare (se folosesc funcţii specializate de gestiune a fişierelor). În continuare, prin specifi-
cator de fişier se va înţelege un nume extern de fişier, conform convenţiilor sistemului de ope-
rare. Specificatorul de fişier poate să conţină strict numele fişierului sau poate conţine şi calea
completă pînă la el.

3.8.1. Nivelul inferior de prelucrare a fişierelor


Nivelul inferior de prelucrare este folosit rar, numai în programele de sistem. La acest
nivel, descrierea fişierelor se realizează în corpul programelor, caracteristicile acestora
obţinîndu-se din context. Maniera de prelucrare este asemănătoare celei de la nivelul sistemu-
lui de operare. Nu există un tip anume de dată, fişierul fiind referit printr-un index care indică
intrarea într-o tabelă de gestiune a resurselor sistemului de operare. Acest index este de tip int
şi se numeşte manipulator de fişier (handle). Manipulatorul este creat şi gestionat de către sis-
temul de operare. Utilizatorul îl foloseşte pentru a indica sistemului fişierul asupra cărui do-
reşte să facă prelucrări.
Pentru utilizarea acestui nivel, în programul C trebuie incluse bibliotecile standard
io.h, stat.h şi fcntl.h.

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:

int open(const char *path,int access[,unsigned mod]);

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:

int read(int nf, void* zonat, unsigned n);

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:

int write(int nf, void* zonat, unsigned n);

Funcţia returnează numărul de octeţi scrişi în fişier; nf este manipulatorul de fişier


(alocat la crearea sau deschiderea fişierului), zonat este un pointer spre zona tampon din care
se face scrierea (aceasta este definită de programator); n este numărul de octeţi care se scriu.
Numărul maxim de octeţi care pot fi citiţi este 65534 (deoarece 65535 – 0xFFF – se reprezin-
tă intern la fel ca -1, indicatorul de eroare). În general, trebuie ca la revenirea din funcţia
write, valoarea returnată să fie egală cu n; dacă este mai mică, s-a produs o eroare (probabil
discul este plin). La scrierea în fişiere text, dacă în fluxul octeţilor care se scriu apare caracte-
rul LF, write va scrie în fişier perechea CR/LF. În caz de eroare, valoarea returnată este -1 şi
se setează variabila errno. Fişierul standard de ieşire (stdout) are manipulatorul 1 iar cel de
eroare (stderr) are manipulatorul 2.
* Material didactic pentru ID * 55

Închiderea unui fişier se realizează prin apelul funcţiei close, care are următorul proto-
tip:
int close(int nf);

Funcţia returnează valoarea 0 (închidere cu succes) sau -1 (eroare); nf este manipulato-


rul de fişier. De asemenea, închiderea unui fişier se realizează automat, dacă programul se
termină prin apelul funcţiei exit.

Poziţionarea într-un fişier se realizează prin apelul funcţiei lseek, care are următorul
prototip:

long lseek(int nf, long offset, int start);

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:

int unlink(const char* numef);

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:

int chmod(const char *cale, int mod);

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.

Verificarea atingerii sfîrşitului de fişier se face folosind funcţia eof:

int eof(int nf);


unde nf este manipulatorul fişierului. Funcţia returnează valoarea 1 dacă pointerul este pozi-
ţionat pe sfîrşitul fişierului, 0 în caz contrat şi -1 în caz de eroare (nu este găsit fişierul – errno
primeşte valoarea EBADF).
Algoritmi în programare 56

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;

/* crearea unui fisier */


handle = open("TEST.$$$", O_CREAT | O_RDWR, S_IREAD | S_IWRITE);

/* scrierea unor date in fisier */


write(handle, msg, strlen(msg));

/* pozitionare la inceputul fisierului */


lseek(handle, 0L, SEEK_SET);

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

3.8.2. Nivelul superior de prelucrare a fişierelor


La acest nivel, un fişier se descrie ca pointer către o structură predefinită (FILE – tabela
de descriere a fişierului (FIB)):

FILE* f;

Tipul FILE (descris în stdio.h) depinde de sistemul de operare.


Fişierul este considerat ca flux de octeţi, din care funcţiile de prelucrare preiau secven-
ţe pe care le tratează într-un anumit fel (sau în care inserează secvenţe de octeţi).
Funcţiile folosite la acest nivel pot fi împărţite în trei categorii: funcţii de prelucrare
generale, funcţii de citire/scriere cu conversie şi funcţii de citire/scriere fără conversie. Funcţi-
ile de prelucrare generală se aplică tuturor fişierelor, indiferent de tipul informaţiei conţinute;
prelucrarea efectuată de acestea nu are nici un efect asupra conţinutului fişierului. Funcţiile
care lucrează cu conversie se aplică fişierelor care conţin informaţie de tip text (linii de text,
separate prin perechea CR/LF, iar la sfîrşit se găseşte caracterul CTRL-Z). Funcţiile care lu-
crează fără conversie se aplică fişierelor care conţin informaţie binară.
Funcţiile de citire/scriere deplasează pointerul de citire/scriere al fişierului, spre
sfîrşitul acestuia, cu un număr de octeţi egal cu numărul de octeţi transferaţi (fără a trece de
sfîrşitul de fişier).
* Material didactic pentru ID * 57

Funcţii de prelucrare generală


Deschiderea şi asignarea se realizează prin apelul funcţiei fopen. Funcţia returnează
un pointer spre o structură de tip FILE (în care sînt înscrise date referitoare la fişierul deschis)
sau NULL dacă fişierul nu se poate deschide:

FILE* fopen(const char* nume_extern,const char* mod);

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

Modurile în care poate fi deschis un fişier sînt prezentate în tabelul 3.1.

Tabelul 3.1. Modurile de deschidere a unui fişier


Mod Scop
a
Deschide un fişier existent pentru adăugare la sfîrşit (extindere) sau îl creează dacă nu
există. Este permisă numai scrierea. Numai pentru fişiere text.
r Deschide un fişier existent numai pentru citire
w
Suprascrie un fişier existent sau creează unul nou, permiţîndu-se numai operaţia de
scriere
a+
Deschide un fişier existent pentru adăugare la sfîrşit (extindere) sau îl creează dacă nu
există. Sînt permise citiri şi scrieri. Numai pentru fişiere text.
r+ Deschide un fişier existent pentru citire şi scriere
w+
Suprascrie un fişier existent sau creează unul nou, permiţîndu-se atît citiri cît şi scri-
eri.

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.

Tabelul 3.2. Moduri uzuale pentru deschiderea fişierelor


Operaţia de gestiune Fişiere text Fişiere binare
Creare w wb
Consultare r rb
Actualizare r+b
Creare şi actualizare w+ rwb, w+b
Extindere a

Închiderea fişierelor se realizează prin apelul funcţiei fclose, care are următorul prototip:

int fclose(FILE* f);

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

Revenirea la începutul fişierului se realizează prin funcţia rewind, cu prototipul:

void rewind(FILE *f);

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.

Testarea sfîrşitului de fişier, se realizează prin apelul macrodefiniţiei feof:

int feof(FILE* f);

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:

int fflush(FILE* f);

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:

int fgetpos(FILE* f,fpos_t* poziţie);

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.

long ftell(FILE* f);

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

Modificarea poziţiei pointerului de citire/scriere se poate face prin poziţionare relativă:

int fseek(FILE* f,long deplasare,int origine);

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:

int fsetpos(FILE* f,const fpos_t poziţie);

Pointerul de citire/scriere se mută în fişierul f la octetul cu numărul indicat de parametrul pozi-


ţie (care poate fi o valoare obţinută prin apelul lui fgetpos).
Ambele funcţii resetează indicatorul de sfîrşit de fişier şi anulează efectele unor even-
tuale apeluri anterioare ale lui ungetc asupra acelui fişier.

Redenumirea sau mutarea unui fişier existent se poate realiza prin apelul funcţiei
rename, care are următorul prototip:

int rename(const char* n_vechi,const char* n_nou);

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:

int remove(const char* cale);

unde cale reprezintă specificatorul fişierului (trebuie să fie închis).

Funcţii de citire/scriere fără conversie


Funcţiile efectuează transferuri de secvenţe de octeţi între memoria internă şi un fişier
de pe disc, fără a interveni asupra conţinutului sau ordinii octeţilor respectivi.

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:

size_t fwrite(const void* ptr,size_t dim,size_t n,FILE* f);

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ţii de citire/scriere cu conversie


Funcţiile efectuează transferuri de secvenţe de octeţi între memoria internă şi un fişier
de pe disc, convertind secvenţa de la reprezentarea internă (binară) la reprezentarea externă
(ASCII) şi invers.
Transferul de caractere se efectuează prin următoarele funcţii:
int fgetc(FILE* f);
int fputc(int c, FILE *f);
int getc(FILE* f);
int putc(int c, FILE *stream);
* Material didactic pentru ID * 61

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.

Transferul de şiruri de caractere se efectuează prin funcţiile:


char* fgets(char* s,int n,FILE* f);
int fputs(const char* s,FILE* f);

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.

Transferul de date cu format controlat este realizat prin funcţiile:


int fprintf(FILE* f,const char* format[,…]);
int fscanf(FILR* f,const char* format[,…]);

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.

Deşi nu lucrează cu fişiere în mod direct, se pot folosi şi funcţiile


int sprintf(char *s,const char *format[,...]);
int sscanf(const char *s,const char *format[,...]);

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

/* se produce eroare la incercarea de citire */


getc(f);
if(ferror(f)) /* s-a produs eroare de I/E? */
{ /* afiseaza mesaj de eroare */
printf("Eroare la citirea din test.ttt\n");
//reseteaza indicatorii de eroare si sfarsit de fisier
clearerr(f);
}
fclose(f);
return 0;
}

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.

Răspunsuri şi comentarii la testele de autoevaluare


1. Articolul este o dată complexă, eterogenă, cu acces direct la componente. În func-
ţie de modul de utilizare, un articol poate fi alocat static sau dinamic.

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

Bibliografia unităţii de învăţare

1. I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C. Uscatu, M. Mircea - Programarea


calculatoarelor. Algoritmi în programare, Ed. ASE Bucureşti, 2007
2. I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C. Uscatu - Programarea calculatoa-
relor. Ştiinţa învăţării unui limbaj de programare. Teorie şi aplicaţii, Ed. ASE Bucureşti,
2003
3. Liviu Negrescu - Limbajele C şi C++ pentru începători, vol. I, II, Ed. Microinformatica,
Cluj-Napoca, 1994
4. I.Gh. Roşca & colectiv - Bazele elaborării programelor. Exerciţii rezolvate şi propuse, Ed.
ASE Bucureşti, 1998
5. B. Ghilic-Micu, I. Gh. Roşca, C. Apostol, M. Stoica - Algoritmi în programare, Ed. ASE,
Bucureşti, 2002
Algoritmi în programare 64

4. Algoritmi de prelucrare a fişierelor de date

Cuprins

Obiectivele unităţii de învăţare ............................................................................................. 64


4.1. Caracteristici generale ale algoritmilor de prelucrare a fişierelor ........................ 64
4.2. Algoritmi de prelucrare a fişierelor binare care nu necesită actualizare ............. 69
4.3. Algoritmi de prelucrare a fişierelor binare care necesită actualizare................... 77
4.3.1. Codificarea externă prin numere relative........................................................ 77
4.3.2. Codificarea internă prin numere relative ........................................................ 79
4.3.3. Corespondenţa internă dintre chei şi numere relative ................................... 80
4.4. Sortarea fişierelor binare memorate dens ............................................................... 90
4.5. Interclasarea fişierelor binare memorate dens........................................................ 94
4.6. Prelucrarea masivelor memorate în fişiere binare.................................................. 95
4.6.1. Prelucrarea vectorilor........................................................................................ 95
4.6.2. Prelucrarea matricelor ...................................................................................... 96
Răspunsuri şi comentarii la testele de autoevaluare ........................................................... 98
Bibliografia unităţii de învăţare............................................................................................ 98
Bibliografie.............................................................................................................................. 98

Obiectivele unităţii de învăţare

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.

4.1. Caracteristici generale ale algoritmilor de prelucrare a fişierelor

Din punct de vedere al operaţiilor de gestiune solicitate de diverse aplicaţii, fişierele


binare se pot grupa în: fişiere care nu sînt actualizate (ţinute la zi) şi fişiere care sînt actualizate.
De obicei, fişierele din prima grupă se regăsesc în aplicaţii matematice sau ca fişiere temporare
şi de tranzacţii în aplicaţii de gestiune economică. Fişierele din cea de-a doua grupă sînt, de
obicei, fişiere permanente (principale) în aplicaţii de gestiune economică şi au particularităţi de
proiectare referitoare, în special, la asigurarea ştergerii şi adăugării de articole.

Organizarea datelor în fişiere memorate pe medii magnetice externe presupune proiec-


tarea unor algoritmi specifici operaţiilor de gestiune a acestora, denumiţi generic algoritmi de
prelucrare a fişierelor de date. Datorită complexităţii aplicaţiilor care prelucrează fişiere este
recomandată aplicarea metodei modularizării algoritmilor şi programelor. Modularizarea pre-
supune ca, pe baza analizei problemei, să se descompună rezolvarea ei în părţi distincte, nu-
mite module, astfel încît fiecare dintre acestea să îndeplinească anumite funcţii. Descompune-
rea se poate realiza în mai multe faze (pe mai multe niveluri), prin metoda top-down.
* Material didactic pentru ID * 65

Criteriile de descompunere în module depind, în mare măsură, de experienţa programatorilor.


Ele se referă, în principal, la: omogenizarea funcţiilor; utilizarea diverselor structuri de date;
separarea funcţiilor de intrare/ieşire de funcţiile de prelucrare; utilizarea unor module deja
existente; utilizarea eficientă a resurselor calculatorului (timp UC, memorie internă, periferie)
etc. Modulele se implementează în program prin subprograme interne sau externe.

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 ÎNCEPUT se realizează o sin-


gură dată, înaintea prelucrării primului articol
al fişierului conducător şi cuprinde următoa-
rele grupe de operaţii:
Operaţii iniţiale standard, obligatorii
oricărui algoritm şi care includ: punerea în
corespondenţă a fişierelor logice cu fişiere
fizice, deschiderea fişierelor, şi, pentru
anumite variante, iniţializarea unei variabi-
le logice pentru sfîrşit de fişier (SF) şi citi-
rea primului articol.
Operaţii iniţiale specifice, facultative,
existenţa lor depinzînd de particularităţile
problemei abordate şi care includ, în prin-
cipal: iniţializări de variabile de total, afi-
şări ale antetului, titlului şi/sau a capului
Fig. 4.1. Schema logică generală a unui algo- de tabel pentru situaţii de ieşire etc.
ritm de prelucrare cu fişier conducător

Modulul PRELUCRARE se execută repetitiv şi cuprinde, pe de o parte, totalitatea ope-


raţiilor de prelucrare a articolului curent al fişierului conducător - operaţii specifice fiecărei
probleme - şi, pe de altă parte, citirea unui articol din fişierul conducător. Ordinea celor două
operaţii (citire şi prelucrare) depinde de varianta de algoritm aleasă.

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

Modalitatea de detectare/tratare a sfîrşitului de fişier conduce la existenţa mai multor variante


ale schemei generale de prelucrare cu fişier conducător, prin forme particulare ale condiţiei
sfîrşit_de_prelucrare. În funcţie de variantele alese, se pot construi scheme logice valabile
pentru toate tipurile de fişiere sau numai pentru fişierele binare.

Scheme valabile pentru toate tipurile de fişiere

Detectarea sfîrşitului de fişier, cu macrodefiniţia feof, caz în care testarea sfîrşitului de


fişier trebuie să urmeze după o operaţie de citire a unui articol. Algoritmul trebuie să conţină o
citire iniţială în modulul ÎNCEPUT şi o citire curentă la sfîrşitul modulului PRELUCRARE -
(figura 4.2). Acest algoritm se poate aplica fişierelor vide sau nevide.

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)

Scheme logice valabile numai pentru fişiere binare


Detectarea sfîrşitului de fişier prin operaţia de citire, verificînd rezultatul întors de
funcţia de citire (fread). Dacă rezultatul este mai mic decît numărul de blocuri de date care
trebuie citite, înseamnă că s-a ajuns la sfîrşitul fişierului. Întrucît, uzual, la o operaţie de citire
se citeşte un articol întreg, în cazul atingerii sfîrşitului de fişier, rezultatul întors de funcţia
fread va fi 0. Rezultatul poate fi preluat într-o variabilă pentru a fi folosit în condiţia de termi-
nare a prelucrării (figura 4.3) sau poate fi verificat direct, folosind apelul funcţiei fread în ex-
presia (condiţia) care controlează sfîrşitul prelucrării (figura 4.4). În ambele variante fişierul
conducător este binar, vid sau nevid.

Prelucrarea unui număr cunoscut de articole, prin determinarea în modulul ÎNCE-


PUT a numărului de articole din fişierul conducător (figura 4.5). Limbajul C nu oferă o
funcţie standard pentru calcularea numărului de articole dintr-un fişier binar, deoarece, din
punctul de vedere al limbajului, fişierele nu conţin articole. Din punctul de vedere al utiliza-
torului, cunoscînd dimensiunea unui articol, se poate calcula numărul de articol de fişier,
împărţind lungimea acestuia la lungimea unui articol (ambele măsurate în număr de octeţi).
Lungimea fişierului este egală cu poziţia curentă, atunci cînd pointerul de citire se află la
sfîrşitul fişierului.
* Material didactic pentru ID * 67

Pentru aflarea numărului de articole, se foloseşte secvenţa următoare:

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.

Fig. 4.4. Detectarea sfîrşitului de fişier (fişier


binar)

Fig. 4.5. Prelucrarea unui fişier cu dimensiu-


ne cunoscută

Caracteristica generală a algoritmilor de prelucrare cu fişier conducător este parcurge-


rea secvenţială a fişierului conducător şi efectuarea unor prelucrări în funcţie de fiecare articol
citit din acesta. Problema care se pune este detectarea sfîrşitului de fişier. În C, macrodefiniţia
feof nu face decît să furnizeze valoarea indicatorului de sfîrşit de fişier, care este setat de ope-
raţia de citire; în program, citirea trebuie să apară înaintea verificării sfîrşitului de fişier. For-
ma generală a algoritmului este:

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

4.2. Algoritmi de prelucrare a fişierelor binare care nu necesită actualizare

Asupra fişierelor binare care nu necesită actualizare se realizează, de obicei, operaţiile


de creare (populare) şi consultare. Dintre operaţiile de actualizare pot fi realizate, fără mari
complicaţii, modificarea şi adăugarea densă de articole.

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.

Sfîrşitul introducerii datelor de la tastatură (şi implicit al procesului de populare a


fişierului) poate fi:
De tip chestionar, prin consultarea utilizatorului, privind continuarea sau nu a introducerii
articolelor. Pentru un volum mare de date, varianta prezintă dezavantajul măririi timpului
de prelucrare.
Convenţional, prin introducerea pentru primul cîmp din articol a unei valori prestabilite, cu
semnificaţie de sfîrşit de prelucrare.
Standard, prin introducerea caracterului CTRL-Z, cu rol de sfîrşit de fişier text.

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

O altă problemă a populării fişierelor binare o reprezintă aşezarea articolelor pe suportul


extern. Din acest punct de vedere se întîlnesc două modalităţi:
Populare densă, prin care articolele se scriu unul după altul, în ordinea în care au fost
furnizate, fără a se lăsa locuri libere (acces secvenţial). Pentru fişierele care nu necesită
actualizare acesta este tipul recomandat.
Populare aleatoare, prin care articolele sînt scrise în casetele (virtuale) ale căror numere
relative sînt furnizate explicit de utilizator (acces direct). Scrierea unui articol se realizează
după poziţionarea pe numărul relativ dorit. La populare, nr_relativ nu este limitat decît de
spaţiul existent pe suportul extern. Metoda are dezavantajul că necesită evidenţa
„articolelor vide”. În cazul fişierelor care nu necesită actualizare, popularea aleatoare se
Algoritmi în programare 70

recomandă numai dacă, după creare, fişierul este dens.

Pentru poziţionarea pe articolul cu numărul relativ n se foloseşte funcţia fseek astfel:


fseek(f, n*sizeof(tip_articol), SEEK_SET);

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>

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

Observaţii: dacă se doreşte crearea fişierului de date cu populare în acces direct,


programul este similar, cu următoarele diferenţe:

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

unde codt este o variabilă independentă în care se citeşte codul de la terminal.

Consultarea fişierelor are numeroase variante, în funcţie de scopul prelucrării. După


modul de regăsire a articolelor în cadrul fişierului, ea poate fi secvenţială, directă sau mixtă.
Consultarea secvenţială presupune regăsirea articolelor în ordinea în care au fost scrise
pe suportul tehnic de informaţii. După numărul articolelor prelucrate, consultarea secvenţială
poate fi: integrală, cînd se prelucrează toate articolele fişierului, începînd cu primul şi terminînd
cu ultimul; cu selecţie, cînd se prelucrează numai acele articole care au una sau mai multe
caracteristici comune (valori identice pentru acelaşi cîmp). După numărul de caracteristici,
selecţia poate fi simplă, dublă, multiplă. Pentru consultarea secvenţială se poate utiliza oricare
din tipurile de algoritmi prezentaţi anterior.

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

Operaţii finale Operaţii finale


generale Nu Grupa 2 TOT2+=val_art

TOT1+=TOT2
Citeşte
STOP articol
Nu

Fig. 4.6. Schema logică – problema cu grade de total

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

Datele se referă la situaţia şcolară a studenţilor dintr-o universitate. Convenim că vectorul de


note conţine valoarea zero acolo nu există notă. Scrieţi un program care creează un raport (într-
un fişier text), cu studenţii grupaţi după următoarele caracteristici: facultate, an de studiu, grupa.
În cadrul grupei studenţii vor fi trecuţi în ordine alfabetică. Pentru fiecare student se înscrie
numărul matricol, numele şi prenumele şi media. Se va calcula media pentru fiecare grupă (ca
medie a studenţilor din grupă), pentru fiecare an din cadrul unei facultăţi (ca medie a mediilor
grupelor componente), pentru fiecare facultate (ca medie a mediilor anilor de studiu) şi media
generală. Cîte caracteristici de grupare şi cîte grade de total sînt implicate în acest program?
(Pentru rezolvare va trebui studiată întîi sortarea fişierelor binare).
Algoritmi în programare 74

Consultarea în acces direct presupune regăsirea articolului după numărul relativ.


Întrucît fişierele sînt considerate ca fluxuri de octeţi, trebuie calculată poziţia articolului dorit în
fişier ca produs între numărul său relativ şi dimensiunea unui articol în octeţi
(nr*sizeof(tip_articol)). Secvenţa care realizează acest lucru este:

fseek(f,nr*sizeof(tip_articol), SEEK_SET);
fread(&art,sizeof(tip_articol), 1, f);

Numărul relativ este furnizat de utilizator şi trebuie să aparţină domeniului 0..dimensiune


fişier-1 (dimensiunea calculată ca număr de articole). Pentru evitarea situaţiilor în care numărul
relativ se află în afara acestui domeniu, se va include în program validarea apartenenţei
numărului relativ la intervalul acceptat. Algoritmul de consultare în acces direct a unui fişier are
un alt fişier conducător (de exemplu tastatura).

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

Adăugarea de articole se realizează, în general, cu tranzacţii de la terminal, similar


operaţiei de populare. Pentru o corectă exploatare ulterioară, adăugarea trebuie să fie densă.
Acest lucru poate fi realizat astfel:
Adăugare la sfîrşit (extindere), după ultimul articol scris. Operaţia se realizează
similar populării în acces secvenţial, după poziţionarea pe marcatorul de sfîrşit de fişier,
apelînd funcţia fseek: fseek(f,0,SEEK_END);

Exemplu
8. Secvenţa generală pentru adăugarea datelor la sfîrşitul fişierului.

{ // citire nume fisier extern


f=fopen(nume_fisier, "rb");
fseek(f,0,SEEK_END); // pozitionare dupa ultimul
// articol scris
printf("Cimp 1: ");
scanf("%d ",&art.cimp_1);
while(!feof(stdin))
{ // -----------------------------------------------
// Preluare de la tastatura a celorlalte
// cimpuri din articol
// -----------------------------------------------
printf("Cimp 1: ");
scanf("%d ",&art.cimp_1);
}
fclose(f)
}

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.

{ // articolele fisierului sînt in ordinea crescatoare a valorii cimpului 1

// citire nume fisier extern *)


f=fopen(nume_fisier, "rb+ ");

// calculare numar de articole din fisier


printf("\nCimp 1: "); // introducerea cimpului dupa care
// sint sortate articolele
scanf("%d ",&art_nou.cimp_1);

while(!feof(stdin)) // adaugarea mai multor articole


{ // -----------------------------------
// Preluare de la tastatura a celorlalte
Algoritmi în programare 76

// cimpuri din articolul de adaugat


// -----------------------------------

// secventa de cautare a pozitiei


// in care se va insera articolul }
fseek(f,0,SEEK_SET); // pozitionare pe inceput de fisier
fread(&art_existent,sizeof(tip_articol),1,f);
while((!feof(f))&&(art_existent.cimp_1<art_nou.cimp_1)
fread(&art_existent,sizeof(tip_articol),1,f);
if(!feof(f))
{ k=ftell(f)-sizeof(tip_articol); //articolul se va insera in pozitia k
for(i=nr_art-1;i>=k;i--)
{ fseek(f,i*sizeof(tip_articol),SEK_SET);
fread(&art_existent,sizeof(tip_articol),1,f);
fwrite(&art_existent,sizeof(tip_articol),1,f);
}
}
else k=nr_art; // articolul se adauga la sfirsitul fisierului
fseek(f,k*sizeof(tip_articol),SEK_SET);
fwrite(&art_nou,sizeof(tip_articol),1,f);
printf("\nCimp 1: "); // introducerea cimpului dupa care
// sint sortate articolele
scanf("%d ",&art_nou.cimp_1);rite('Cimp 1: ')
}
fclose(f);
}

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.

O problemă importantă rămîne selectarea cîmpurilor care se modifică, pentru fiecare


articol în parte.
O variantă simplă este afişarea vechii valori, urmată de introducerea noii valori, în
variabile independente de tip şir de caractere. În cazul în care şirul introdus este vid (s-a apăsat
numai ENTER), respectivul cîmp, prin convenţie, nu se modifică. Altfel, cîmpului respectiv i se
va atribui valoarea citită în variabila independentă, eventual prin conversie, pentru cîmpurile
numerice.

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

printf("Codul: %d - ",art.cod); //afisare vechea valoare


fflush(stdin);
* Material didactic pentru ID * 77

gets(cods); // citire noua valoare; cods este de tip sir


if(strlen(cods))
{ art.cod=atoi(cods); // conversie din ASCII in binar
printf("Denumire: %s - ",art.den); // afisare vechea valoare
gets(dens); // citire noua valoare
if(strlen(dens)
strcpy(dens,art.den); // copiere noua valoare
// ----------------------------------
// Introducerea celorlalte cimpuri
// din articol
// ----------------------------------
// repozitionare pe articol
feek(f,ftell(f)-sizeof(tip_articol),SEEK_SET);
// rescriere articol modificat
fwrite(&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.

4.3. Algoritmi de prelucrare a fişierelor binare care necesită actualizare

Prelucrarea fişierelor binare care necesită actualizare trebuie să asigure posibilitatea


ştergerii articolelor şi să elimine riscul de suprascriere a articolelor adăugate. Pentru aceasta,
trebuie proiectate structuri particulare de articole şi concepute operaţii de gestiune specifice.

Fără a epuiza multitudinea soluţiilor de rezolvare a problemelor de gestiune a fişierelor


care necesită actualizare, în continuare, se prezintă cîteva soluţii posibile. În orice situaţie,
limitările de regăsire prin acces secvenţial sau relativ a articolelor în fişier reduc aria folosirii
limbajului în probleme de gestiune. Marele inconvenient îl constituie lipsa accesului după cheie,
cel care corespunde cel mai bine gestiunii în sistemele reale.

În cele ce urmează se analizează trei tipuri de probleme: probleme în care se utilizează


asocierea externă a numărului relativ la articolul corespunzător (codificare prin număr relativ);
probleme în care se utilizează asocierea internă a numărului relativ la articolul corespunzător iar
acesta poate emana extern (se generează nomenclatoare după fiecare actualizare de fişier);
probleme în care se utilizează extern coduri (chei) şi intern numere relative.

4.3.1. Codificarea externă prin numere relative


Nomenclatorul de articole conţine numărul relativ al fiecăruia dintre ele. Nomenclatorul
este elaborat extern (automat sau neautomat). Orice operaţie de regăsire în acces relativ
presupune introducerea din exterior a numărului relativ. La crearea iniţială, fiecare articol este
înscris în poziţia indicată de numărul său relativ predefinit. Asigurarea ştergerii şi adăugării
controlate poate fi făcută în diverse moduri.
Algoritmi în programare 78

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.

Scrierea în acces direct presupune furnizarea numărului relativ (nr) al articolului. În


funcţie de valoarea lui nr se disting următoarele situaţii:
- dacă nr<dimensiune fişier, se citeşte articolul respectiv din fişier şi adăugarea este
permisă numai dacă IS=0;
- dacă nr>=FileSize(f), are loc extinderea fişierului cu preformarea articolelor cu
numerele relative cuprinse în domeniul dimensiune fişier..nr-1. Noul articol se scrie pe poziţia
nr.
Se remarcă faptul că scrierea în acces direct permite preformarea iniţială cu zero
articole.
Scrierea în acces secvenţial se face fără verificare de existenţă. Scrierea are loc în
poziţia dată de pointerul curent. Procedura face IS=1. Utilizarea ei se recomandă numai la
popularea densă.
Citirea în acces direct presupune furnizarea numărului relativ (nr). Ea verifică dacă
IS=1.
Citirea în acces secvenţial analizează articolele începînd cu cel de la pointerul curent.
Articolele cu IS=0 sînt ignorate, pînă la întîlnirea primului articol cu IS=1 sau pînă se ajunge la
sfîrşit de fişier.
Ştergerea se realizează în acces direct. Ea presupune citirea articolului şi dacă ştergerea
este permisă (IS=1), se modifică indicatorul de stare (IS=0) şi se scrie articolul pe vechiul loc.
Rescrierea realizează scrierea unui articol în poziţia ftell(f)-sizeof(tip_articol), dacă
vechiul articol din această poziţie are IS=1.

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.

Folosirea articolului zero ca tabelă de ocupare în fişier.


Fiecărui articol din fişier îi corespunde cîte un octet în primul articol: articolului cu
numărul relativ i îi corespunde octetul a[i]. Primul articol are structura char a[max], unde max
este o constantă care indică numărul maxim de articole pe care le poate avea fişierul pe durata
existenţei sale. Dacă articolul i este prezent, a[i]=1; dacă articolul i este inactiv (inexistent sau
şters), a[i]=0.
* Material didactic pentru ID * 79

Cu această structură, operaţiile de acces la articole se realizează în următoarele condiţii:


scrierea în fişier a articolului cu numărul relativ i este permisă numai dacă a[i]=0; citirea din
fişier a articolului cu numărul relativ i este permisă numai dacă a[i]=1.
Ştergerea articolului i presupune verificarea existenţei sale (a[i]=1) şi realizarea
operaţiei a[i]=0. Adăugarea unui articol i presupune verificarea inexistenţei lui (a[i]=0),
înscrierea articolului şi realizarea operaţiei a[i]=1.

Utilizarea acestei modalităţi necesită încărcarea iniţială în memoria principală a


articolului cu numărul relativ zero. În programele care realizează ştergeri sau/şi adăugări,
înainte de închiderea fişierului trebuie rescris articolul zero în fişier.

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.

4.3.2. Codificarea internă prin numere relative


Înscrierea articolelor în fişiere, chiar cea iniţială, se face în primele articole inactive,
asociindu-se astfel intern un număr relativ fiecărui articol.
Deosebirea esenţială între această soluţie şi cea prezentată anterior, constă în modul de
realizare a adăugării secvenţiale. După fiecare sesiune de actualizare va trebui listat
nomenclatorul de coduri interne (numere relative). Articolelor din fişier li se asociază structura
din figura 4.7. Preformarea, consultarea, modificarea şi ştergerea sînt similare celor prezentate
în §4.3.1. Adăugarea unui articol se realizează în condiţii diferite, după cum această operaţie are
loc printre articolele existente, respectiv după ultimul articol (extindere). În ambele situaţii,
condiţiile de realizare sînt determinate de modul de acces folosit: secvenţial sau direct.

Adăugarea în acces secvenţial se bazează pe presupunerea că utilizatorul nu impune o


corespondenţă prestabilită între conţinut şi numărul articolului, adică în alţi termeni, că se
acceptă o codificare automată. Adăugarea în acces secvenţial poate fi utilizată în două variante:

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:

Folosirea articolului zero pentru colectarea numărului articolelor libere, într-o


structură de forma celei din figura 4.8.

nal al[1] al[2] ... al[nal]


Articolul 0
WORD WORD WORD ... WORD
Fig. 4.8. Structura articolului zero, pentru gestiunea articolelor libere

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

Fig. 4.9. Gestionarea articolelor libere prin liste

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

4.3.3. Corespondenţa internă dintre chei şi numere relative


Majoritatea aplicaţiilor de gestiune economică utilizează fişiere de date în care articolele
trebuie regăsite după valorile unui cîmp de identificare, numit cheie. Problema corespondenţei
între chei şi numerele relative ale articolelor din fişierul de date se poate rezolva prin
intermediul unui fişier binar suplimentar, cu rol de tabelă de indexuri. O astfel de organizare se
numeşte indexată. Articolele fişierului tabelă de indexuri au structura din figura 4.10.
IS Cheie Număr relativ (nr)
Fig. 4.10. Structura articolului din tabela de indexuri
* Material didactic pentru ID * 81

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

Operaţiile de acces la nivel de fişier sînt deschiderea şi închiderea fişierului de date. La


rîndul ei, deschiderea poate fi pentru creare (ca fişier nou) sau pentru consultare şi întreţinere
(ca fişier vechi). Procedurile realizează, pe lîngă operaţiile asupra fişierului de date şi
gestionarea automată a tabelei de indexuri: formarea numelui său extern, asignarea numelui
fizic la numele logic, deschiderea ca fişier nou sau vechi, închiderea. Operaţiile de gestiune ce
se pot realiza cu fişierele de date astfel organizate sînt:

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 secvenţial presupune regăsirea articolelor în ordinea strict crescătoare


a valorilor cheii. Ea se realizează cu ajutorul funcţiei de citire în acces secvenţial, care
detectează şi sfîrşitul fişierului de date.

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.

Adăugarea de articole se realizează utilizînd funcţia de scriere în acces direct. Articolele


pot fi furnizate în orice ordine a valorilor cheii. Eroarea de cheie invalidă apare la tentativa de
scriere a unui articol a cărui cheie este egală cu una deja existentă.

Modificarea unor cîmpuri din articol se realizează în următoarele etape:


citirea articolului de modificat (în acces secvenţial sau direct);
modificarea, în zona articol corespunzătoare, a cîmpurilor dorite, cu excepţia cîmpului
cheie;
rescrierea articolului, cu procedura de rescriere. Eroarea de cheie invalidă apare în cazul
tentativei de rescriere a unui articol a cărui cheie este diferită de cea a articolului citit
anterior.

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

cheie denumire preţ cantitate


Fig. 4.11. Structura articolului din tabela de indexuri

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:

typedef struct{ char cheie[7];


char den[35];
float pu;
float cant;
} ARTICOL; //tipul articol din fisierul de date

typedef struct{ char is;


char cheie[7];
long nr_rel;
} ART_INDEX; //tipul articol din tabela de indexuri

FILE* ind; //fisierul index


char nume_index[20]; //numele extern al fisierului index

Pentru implementarea operaţiilor de gestiune specifice unui fişier organizat indexat sînt
necesare următoarele subprograme:

Funcţia de deschidere a tabelei index ca fişier nou, cu prototipul

void new_index(char *nume);

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 de deschidere a tabelei de indexuri, pentru consultare şi întreţinere, cu


prototipul

void open_index(char *nume);

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.

Funcţia de închidere a tabelei de indexuri, cu prototipul

void closeindex();

Funcţia realizează închiderea tabelei de indexuri asociate fişierului de date.


* Material didactic pentru ID * 83

Funcţia pentru citirea în acces secvenţial a unui articol din fişierul de date, cu prototipul

int ReadSec(fisier f,articol *a);

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

int ReadKey(fisier f,articol *a,char *Key);

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

int WriteSec(fisier f,articol a);

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

int WriteKey(fisier f,articol a);


Algoritmi în programare 84

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 pentru ştergerea în acces secvenţial a unui articol, cu prototipul


int DeleteSec();

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 pentru ştergerea în acces direct a unui articol, cu prototipul


int DeleteKey(char *Key);

Funcţia primeşte ca parametru de intrare cheia articolului care va fi şters şi returnează


- 1, dacă ştergerea a fost posibilă;
- 0, în caz contrar.
Funcţia şterge logic din fişierul de date articolul a cărui cheie este primită ca parame-
tru. Ştergerea este realizată fizic din tabela de indexuri, analog ştergerii în acces secvenţial.
Funcţia returnează valoarea 0, corespunzătoare situaţiei de eroare, dacă Key nu este regăsită în
tabela de indexuri. Căutarea este realizată prin apelul funcţiei SeekKey.
Pentru implementarea funcţiilor descrise mai sus, sînt utilizate următoarele funcţii au-
xiliare:

Funcţia pentru sortarea tabelei de indexuri, cu eliminarea articolelor cu stare 0, cu


prototipul
void Sort();

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 pentru căutarea articolului cu cheie dată, cu prototipul

int SeekKey(char *Key)

Funcţia primeşte ca parametru de intrare cheia articolului căutat şi returnează


- 1, dacă articolul a fost găsit;
- 0, în caz contrar.
* Material didactic pentru ID * 85

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>

#define fisier FILE*


typedef struct{ char cheie[7];
char den[35];
float pu;
float cant;
} ARTICOL; //tipul articol din fisierul de date

typedef struct{ char is;


char cheie[7];
long nr_rel;
} ART_INDEX; //tipul articol din tabela index

fisier ind; //fisierul index


char nume_index[20]; //numele extern al fisierului index

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

/* cautarea articolului cu cheia Key si plasarea pointerului de fisier in ta-


bela de indexuri pe articolul respectiv*/

int SeekKey(char *Key)


{ long ls=0, ld, m, n;
ART_INDEX a;
int gasit=0;
fseek(ind,0,SEEK_END);
n=ftell(ind)/sizeof(ART_INDEX);
ld=n-1;
while((ls<=ld)&&(!gasit))
{ m=(ls+ld)/2;
fseek(ind,m*sizeof(a),SEEK_SET);
fread(&a,sizeof(a),1,ind);
if(strcmp(a.cheie,Key)==0) gasit=1;
else if(strcmp(a.cheie,Key)>0) ld=m-1;
else ls=m+1;
}
if(gasit) fseek(ind,m*sizeof(a),SEEK_SET);
return gasit;
}

void new_index(char *nume)


{ strcpy(nume_index,nume);
strcat(nume_index,".idx");
ind=fopen(nume_index,"wb+");
}

void open_index(char *nume)


{ strcpy(nume_index,nume);
strcat(nume_index,".idx");
ind=fopen(nume_index,"rb+");
}

void close_index()
{ fclose(ind);
}

int ReadSec(fisier f,ARTICOL *a)


{ ART_INDEX a1;
int r;
fread(&a1,sizeof(a1),1,ind);
if(feof(ind))r=0;
else { fseek(f,a1.nr_rel*sizeof(*a),SEEK_SET);
fread(a,sizeof(*a),1,f);
r=1;
}
return r;
}

int ReadKey(fisier f,ARTICOL *a,char *Key)


{ ART_INDEX a1;
int r;
if(SeekKey(Key))
{ fread(&a1,sizeof(a1),1,ind);
fseek(f,a1.nr_rel*sizeof(*a),SEEK_SET);
fread(a,sizeof(*a),1,f);
r=1;
}
else r=0;
return r;
}

int WriteSec(fisier f,ARTICOL a)


{ ART_INDEX a1, ai;
long n, nl;
int r;
fseek(ind,0,SEEK_END);
* Material didactic pentru ID * 87

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 WriteKey(fisier f,ARTICOL a)


{ char Key[7];
ART_INDEX a1;
long n;
strcpy(Key,a.cheie);
if(SeekKey(Key)) r=0;
else { a1.is=1;
strcpy(a1.cheie,a.cheie);
fseek(f,0,SEEK_END);
n=ftell(f)/sizeof(a);
a1.nr_rel=n;
fwrite(&a,sizeof(a),1,f);
fseek(ind,0,SEEK_END);
fwrite(&a1,sizeof(a1),1,ind);
Sort();
r=1;
}
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;
}

int DeleteKey(char *Key)


{ int r;
if(SeekKey(Key))
r=DeleteSec();
else r=0;
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

printf(" numele fisierului de date din care stergeti:");


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);
printf("\nStergerea in acces direct dupa cheie\n");
printf("Introduceti cheia:");
fflush(stdin);
gets(Key);
while(!feof(stdin))
{ if(ReadKey(f,&a,Key))
{ printf("Articolul:\n");
printf("Denumire:%20s\n",a.den);
printf("Pret:%7.2f\n",a.pu);
printf("Cantitate:%8.2f\n\n",a.cant);
printf("Doriti stergerea?(D/Altceva)");
r=getch();
if(r=='D')
{ i=DeleteKey(Key);
printf("Stergere efectuata");
}
else printf("Stergerea nu a fost efectuata");
}
else printf("Nu exista articol");
getch();
clrscr();
printf("Introduceti cheia:");
fflush(stdin);
gets(a.cheie);
}
fclose(f);
close_index();
getch();
}
}

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

4.4. Sortarea fişierelor binare memorate dens

Operaţia de sortare a unui fişier binar presupune aranjarea articolelor în ordinea


crescătoare (descrescătoare) a valorilor unei zone, numită cheie de sortare. În cazul în care
cheia de sortare este formată dintr-un singur cîmp din cadrul articolului, operaţia se numeşte
sortare simplă. Sortarea multiplă presupune aranjarea articolelor după valorile a două sau mai
multe cîmpuri, alcătuind, prin juxtapunere, cheia de sortare. Juxtapunerea cîmpurilor (nu
neapărat adiacente în cadrul articolului) se realizează pe lungimea efectivă a lor, alcătuind
forma canonică a cheii de sortare. De exemplu, dacă NUME şi PRENUME sînt două cîmpuri
distincte, declarate de tip şir de caractere, forma canonică a cheii de sortare după nume şi
prenume este dată de lungimea efectivă a fiecărei date de tip şir.

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.

Sortarea în memorie este o metodă rapidă şi presupune: citirea întregului fişier în


memoria principală, într-o structură internă de date (vector, arbore bina de sortare); sortarea
efectivă după cheia de sortare (în cazul folosirii unui vector, operaţia nu este necesară dacă se
foloseşte arbore de sortare); recrearea fişierului pe disc.

Atenţie: datorită dimensiunii mari a fişierelor de date şi dimensiunii limitate a


memoriei, aceasta metodă este contraindicată. Metoda se poate aplica numai fişierelor
de dimensiune mică.
Această metodă poate fi implementată în mai multe variante:

Sortarea cu vehicularea întregului articol, presupune memorarea întregului fişier într-


un vector de articole. Compararea pentru sortare se va realiza pe cîmpul cheie de sortare, însă
interschimbarea se realizează la nivelul întregului articol.

Exemplu
15. Sortarea cu vehicularea întregului articol.

typedef struct { char grupa;


char nume_student[30];
float medie;
} STUDENT;
* Material didactic pentru ID * 91

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

// citirea fisierului initial in memorie


for(i=0;i<n;i++)
fread(&x[i],sizeof(STUDENT),1,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);

//rescrierea fiierului pe disc


for(i=0;i<n;i++)
fwrite(&x[i],sizeof(STUDENT),1,f);
fclose (f); }

Sortarea cu vehicularea cheii şi indexului, presupune memorarea într-un vector numai a


valorii cheii de sortare, împreună cu numărul relativ al articolului din fişierul iniţial (indexul).
Interschimbarea se va realiza la nivelul cheii de sortare, rezultînd în final ordinea în care
articolele vor fi scrise în fişier. Deoarece articolele, în întregime, sînt rezidente pe disc, fişierul
sortat va fi creat cu un alt nume fizic, în acces secvenţial, preluînd articolele din fişierul iniţial,
în acces direct. Cheile de sortare şi indexurile pot fi memorate în vectori distincţi sau într-unul
singur, cu elemente de tip articol.

Exemplu
16. Sortare cu vehicularea cheii şi indexului.

typedef struct { char grupa;


char nume_student[30];
float medie;
} STUDENT;

typedef struct { float medie;


int index;
} CHEIE;
void main()
{ FILE *f,*g;
STUDENT y;
CHEIE aux,x[250];
int i,j,n;
f=fopen("STUDENT.DAT","rb");
fseek(f,0,SEEK_END);
n:=ftell(f)/sizeof(STUDENT);
rewind(f);
g=fopen("STUDENTS.DAT","wb");

// ----- incarcare chei si indexuri in memorie -----


for(i=0;i<n;i++)
{ fread(&y,sizeof(STUDENT),1,f);
x[i].medie=y.medie;
x[i].index=i;
}
Algoritmi în programare 92

// ----- sortare vector de chei --------------------


for(i=0;i<n-1;i++)
for(j=i+1;j<n;j++)
if(x[i].medie>x[j].medie)
{ aux=x[i];
x[i]:=x[j];
x[j]:=aux
}

// ----- creare fisier nou, sortat -----------------


for(i=0;i<n;i++)
{ fseek(f,x[i].index*sizeof(STUDENT),SEEK_SET);
fread(&y,sizeof(STUDENT),1,f);
fwrite(&y,sizeof(STUDENT),1,g);
}
fclose(f);
fclose(g);
unlink(f);
rename("STUDENTS.DAT","STUDENT.DAT");
}

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.

typedef struct { char grupa;


char nume_student[30];
float medie;
}STUDENT;

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

4.5. Interclasarea fişierelor binare memorate dens

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

Fig. 4.12. Interclasarea a n fişiere

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

4.6. Prelucrarea masivelor memorate în fişiere binare

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.

4.6.1. Prelucrarea vectorilor


De regulă, vectorii se memorează dens. Numărul relativ al articolului depinde de rangul
elementului în cadrul vectorului, astfel:
nr_relativ = rang(xi)+1 = i+1, pentru i=0..n-1, dacă articolul cu numărul relativ 0, fie nu este
utilizat (caz în care dimensiunea vectorului este n = dimensiune fişier-1), fie memorează
numărul efectiv de componente ale vectorului;
nr_relativ = rang(xi) = i, pentru i=0..n, dacă vectorul se memorează începînd cu primul
articol (caz în care dimensiunea vectorului este n =dimensiunea fişierului).

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

4.6.2. Prelucrarea matricelor


O matrice poate fi memorată într-un fişier binar nedens (similar memorării în MP) sau
dens, în ordine lexicografică sau invers lexicografică. Numărul relativ al elementului aij se
determină pe baza funcţiei rang, astfel:
rang(aij) = i * nr_coloane + j, în cazul memorării lexicografice, unde nr_coloane este fie
numărul coloanelor efective (populare densă), fie numărul coloanelor rezervate (populare
nedensă);
rang(aij) = j * nr_linii + i, în cazul memorării invers lexicografice, unde nr_linii este fie
numărul liniilor efective (populare densă), fie numărul liniilor rezervate (populare nedensă).

Fie m şi n numărul liniilor, respectiv coloanelor efective şi mr şi nr numărul liniilor,


respectiv coloanelor rezervate (mr şi nr corespund dimensiunilor maxime din declaraţia unui
masiv aflat în memoria principală). Pentru ca fişierul să conţină informaţii complete despre
matrice, trebuie să memoreze, pe lîngă elementele ei, şi:
m (sau n), în cazul memorării dense. Cînd se memorează m, n se determină împărţind
dimensiunea fişierului (mai puţin primul articol, unde se află m) la m; cînd se memorează n,
m se determină împărţind dimensiunea fişierului (mai puţin primul articol, unde se află n) la
n. Funcţia rang depinde de m sau n, după cum matricea este memorată invers lexicografic
sau lexicografic;
n şi nr, în cazul memorării nedense în ordine lexicografică. m se determină împărţind
dimensiunea fişierului (mai puţin primele două articole, unde se află n şi nr) la nr, iar mr
nu are relevanţă. Funcţia rang depinde de nr;
m şi mr, în cazul memorării nedense în ordine invers lexicografică. N se determină
împărţind dimensiunea fişierului (mai puţin primele două articole, unde se află m şi mr) la
mr, iar nr nu are relevanţă. Funcţia rang depinde de mr.
Funcţia rang se calculează şi se utilizează numai dacă problema de rezolvat implică
parcurgerea matricei în altă ordine decît cea în care este memorată în fişier, deci consultarea
acestuia se realizează în acces direct.

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

Răspunsuri şi comentarii la testele de autoevaluare


2. Trebuie calculate 12 sume, corespunzător celor 12 luni ale anului. Se iniţializează
cele 12 sume cu zero, apoi se parcurge secvenţial fişierul şi, la fiecare articol, se ada-
ugă producţia valorică a fiecărei luni la suma corespunzătoare. La terminarea parcurgeri fişie-
rului se determină valoarea maximă dintre cele 12 sume. Ea va indica luna (lunile) cerute.

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

Bibliografia unităţii de învăţare

1. I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C. Uscatu, M. Mircea - Programarea


calculatoarelor. Algoritmi în programare, Ed. ASE Bucureşti, 2007
2. I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C. Uscatu - Programarea calculatoa-
relor. Ştiinţa învăţării unui limbaj de programare. Teorie şi aplicaţii, Ed. ASE Bucureşti,
2003
3. I.Gh. Roşca & colectiv - Bazele elaborării programelor. Exerciţii rezolvate şi propuse, Ed.
ASE Bucureşti, 1998
4. B. Ghilic-Micu, I. Gh. Roşca, C. Apostol, M. Stoica - Algoritmi în programare, Ed. ASE,
Bucureşti, 2002
5. Liviu Negrescu - Limbajele C şi C++ pentru începători, vol. I, II, Ed. Microinformatica,
Cluj-Napoca, 1994
* Material didactic pentru ID * 99

Bibliografie

1. I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C. Uscatu, M. Mircea − Programarea


calculatoarelor. Algoritmi în programare, Ed. ASE Bucureşti, 2007
2. I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C. Uscatu − Programarea calculatoarelor.
Ştiinţa învăţării unui limbaj de programare. Teorie şi aplicaţii, Ed. ASE Bucureşti, 2003
3. A. Iorgulescu − Metode numerice şi programe Pascal, Ed. Inforec, Bucureşti, 1996 (para-
grafele 2.2.1, 2.2.2, 2.2.3, 4.2.1, 4.2.2)
4. I.Gh. Roşca & colectiv − Bazele elaborării programelor. Exerciţii rezolvate şi propuse, Ed.
ASE Bucureşti, 1998
5. B. Ghilic-Micu, I. Gh. Roşca, C. Apostol, M. Stoica − Algoritmi în programare, Ed. ASE,
Bucureşti, 2002
6. Liviu Negrescu − Limbajele C şi C++ pentru începători, vol. I, II, Ed. Microinformatica,
Cluj-Napoca, 1994