Sunteți pe pagina 1din 153

1

PROGRAMAREA CALCULATOARELOR
IN LIMBAJUL C

1. Introducere n programare. Limbajul C


Algoritmi si programe . . . . . . . . . . . . . . . . . . . . . . . 5
Dezvoltarea de programe . . . . . . . . . . . . . . . . . . . . . 7
Limbajul de programare C . . . . . . . . . . . . . . . . . . . . 8
Elementele componente ale unui program . . . . . . . . 9
Conventii lexicale ale limbajului C . . . . . . . . . . . . .10
Structura programelor C . . . . . . . . . . . . . . . . . . . . . 12
Directive preprocesor . . . . . . . . . . . . . . . . . . . . . . . 13

2. Date si prelucrri
Variabile si constante . . . . . . . . . . . . . . . . . . . . . . . 15
Tipuri de date n limbajul C . . . . . . . . . . . . . . . . . . 15
Constante n limbajul C . . . . . . . . . . . . . . . . . . . . . 17
Operatori si expresii aritmetice n C . . . . . . . . . . . 18
Erori de reprezentare a numerelor . . . . . . . . . . . . . 20
Prelucrri la nivel de bit . . . . . . . . . . . . . . . . . . . . . 22
Ordinea de evaluare a expresiilor . . . . . . . . . . . . . . 23
Instructiuni expresie n C . . . . . . . . . . . . . . . . . . . . 24
Functii standard de intrare-iesire . . . . . . . . . . . . . . 25

3. Prelucrri conditionate
Structuri de control . . . . . . . . . . . . . . . . . . . . . . . . . 27
Bloc de instructiuni . . . . . . . . . . . . . . . . . . . . . . . . 27
Instructiunea "if" . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Operatori de relatie si logici . . . . . . . . . . . . . . . . . . 30
Expresii conditionale . . . . . . . . . . . . . . . . . . . . . . . 33
Instructiunea "switch" . . . . . . . . . . . . . . . . . . . . . . . 34
Macroinstructiunea assert . . . . . . . . . . . . . . . . . . 36

http://elth.srv.ro/

4. Prelucrri repetitive n C
Instructiunea "while" . . . . . . . . . . . . . . . . . . . . . . . 37
Instructiunea "for" . . . . . . . . . . . . . . . . . . . . . . . . . 38
Instructiunea "do" . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Instructiunile "break" si "continue" . . . . . . . . . . . . 40
Vectori n limbajul C . . . . . . . . . . . . . . . . . . . . . . . 42
Matrice n limbajul C . . . . . . . . . . . . . . . . . . . . . . 43
Programare structurat n C . . . . . . . . . . . . . . . . . . 45

5. Programare modular n C
Importanta functiilor n programare . . . . . . . . . . . . 47
Utilizarea functiilor in C . . . . . . . . . . . . . . . . . . . . . 47
Definirea de functii in C . . . . . . . . . . . . . . . . . . . . . 49
Instructiunea return . . . . . . . . . . . . . . . . . . . . . . . 50
Transmiterea de date intre functii . . . . . . . . . . . . . 52
Functii recursive . . . . . . . . . . . . . . . . . . . . . . . . . . . .54
Biblioteci de functii . . . . . . . . . . . . . . . . . . . . . . . . . 56

6. Tipuri pointer n C
Variabile pointer . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Operatii cu pointeri la date . . . . . . . . . . . . . . . . . . . 58
Vectori si pointeri . . . . . . . . . . . . . . . . . . . . . . . . . . 60
Pointeri n functii . . . . . . . . . . . . . . . . . . . . . . . . . . 62
Pointeri la functii . . . . . . . . . . . . . . . . . . . . . . . . . . 64

7. Operatii cu siruri de caractere n C


Memorarea sirurilor de caractere n C . . . . . . . . . . 67
Erori uzuale la operatii cu siruri de caractere . . . . . 68
Functii standard pentru operatii cu siruri . . . . . . . . 70
Definirea de noi functii pe siruri de caractere . . . . 72
Extragerea de cuvinte dintr-un text . . . . . . . . . . . . 73
Cutarea si nlocuirea de siruri . . . . . . . . . . . . . . . . 75

http://elth.srv.ro/

8. Alocarea dinamica a memoriei n C


Clase de memorare n C . . . . . . . . . . . . . . . . . . . . . 77
Functii de alocare si eliberare a memoriei . . . . . . . 78
Vectori alocati dinamic . . . . . . . . . . . . . . . . . . . . . .79
Vectori de pointeri la date alocate dinamic . . . . . . 80
Argumente n linia de comand . . . . . . . . . . . . . . . 82
Matrice alocate dinamic . . . . . . . . . . . . . . . . . . . . . 83

9. Tipuri structur n C
Definirea de tipuri si variabile structur . . . . . . . . . 85
Utilizarea tipurilor structur . . . . . . . . . . . . . . . . . . 86
Functii cu argumente si rezultat structur . . . . . . . 88
Definirea unor noi tipuri de date . . . . . . . . . . . . . . 89
Structuri cu continut variabil . . . . . . . . . . . . . . . . . 90
Structuri predefinite . . . . . . . . . . . . . . . . . . . . . . . . 92
Structuri legate prin pointeri . . . . . . . . . . . . . . . . . . 93

10. Fisiere de date n C


Tipuri de fisiere . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
Functii pentru deschidere si nchidere fisiere . . . . . 96
Functii de citire-scriere n fisiere text . . . . . . . . . . . 98
Functii de citire-scriere cu format . . . . . . . . . . . . . . 99
Functii de acces secvential la fisiere binare . . . . . . 100
Functii pentru acces direct la date . . . . . . . . . . . . . 102
Descriptori de format n functii de intrare-iesire . . 103

11. Tehnici de programare n C


Stil de programare . . . . . . . . . . . . . . . . . . . . . . . . . 105
Conventii de scriere a programelor . . . . . . . . . . . . .106
Constructii idiomatice . . . . . . . . . . . . . . . . . . . . . . 108
Portabilitatea programelor . . . . . . . . . . . . . . . . . . . .110
Erori uzuale n programe C . . . . . . . . . . . . . . . . . . . 111
Definirea si utilizarea de functii . . . . . . . . . . . . . . . 113

http://elth.srv.ro/

12. Tehnici de programare specifice programelor mari


Particularitti ale programelor mari . . . . . . . . . . . . 117
Compilri separate si fisiere proiect . . . . . . . . . . . . 118
Fisiere antet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
Directive preprocesor utile n programele mari . . . . 121
Proiectul initial . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
Extinderea programului . . . . . . . . . . . . . . . . . . . . . . 126
Imbunttirea programului . . . . . . . . . . . . . . . . . . . . 128
Concluzii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130

13. Programare generic n C


Structuri de date si algoritmi . . . . . . . . . . . . . . . . . . 131
Colectii de date . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
Colectii de date generice . . . . . . . .. . . . . . . . . . . . . . 132
Functii generice standard n C . . . . . . . . . . . . . . . . . 133
Utilizarea de tipuri neprecizate . . . . . . . . . . . . . . . . . 134
Utilizarea de pointeri la void . . . . . . . . . . . . . . . . . 136
Tipuri abstracte de date . . . . . . . . . . . . . . . . . . . . . . . 138

14. Diferente ntre limbajele C si C++


Diferente de sintax . . . . . . . . . . . . . . . . . . . . . . . . . . 141
Diferente la functii . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
Operatori pentru alocare dinamic . . . . . . . . . . . . . . . 143
Tipuri referint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
Fluxuri de intrare-iesire . . . . . . . . . . . . . . . . . . . . . . . 146
Tipuri clas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
Supradefinirea operatorilor . . . . . . . . . . . . . . . . . . . . .149

http://elth.srv.ro/

5
1. Introducere n programare. Limbajul C.
Algoritmi si programe
Un algoritm este o metod de rezolvare a unei probleme printr-o succesiune
de operatii simple. Numrul de operatii este de obicei foarte mare, dar finit.
Spre deosebire de aplicarea unor formule de calcul, un algoritm contine
operatii executate conditionat, numai pentru anumite date, si operatii repetate
de un numr de ori, n functie de datele problemei. Exemplul clasic este
algoritmul lui Euclid pentru determinarea celui mai mare divizor comun a doi
ntregi, care nu poate fi exprimat sub forma unei expresii (formule).
Tipic pentru un algoritm este faptul c anumite operatii se execut
conditionat (n functie de valorile datelor initiale), iar alte operatii se execut
n mod repetat (iar numrul de repetri poate depinde de datele initiale).
Practic nu exist un program fr decizii si cicluri, deci un program n care s
se execute mereu aceleasi operatii, n aceeasi ordine, indiferent de datele
initiale. Altfel spus, anumite operatii dintr-un program pot s nu fie executate
de loc sau s fie executate de un numr de ori, functie de datele initiale.
Algoritmii mai simpli pot fi exprimati direct ntr-un limbaj de programare,
dar pentru un algoritm mai complex se practic descrierea algoritmului fie sub
form grafic (organigrame sau scheme logice), fie folosind un pseudocod,
ca un text intermediar ntre limbajul natural si un limbaj de programare. Un
pseudocod are reguli mai putine si descrie numai operatiile de prelucrare (nu
si variabilele folosite). Nu exist un pseudocod standardizat sau unanim
acceptat. Descrierea unor prelucrri n pseudocod se poate face la diferite
niveluri de detaliere. Exemplu de algoritm pentru afisarea numerelor perfecte
mai mici ca un numr n dat, descris ntr-un pseudocod:
repet pentru fiecare ntreg m ntre 2 si n
calcul sum s a divizorilor lui m
dac m = s atunci
scrie m

sau, la un nivel de detaliere mai aproape de un program n C:


repet pentru fiecare ntreg m ntre 2 si n
s=0
repeta pentru fiecare ntreg d ntre 1 si m
daca d este divizor al lui m atunci
aduna d la s
dac m = s atunci
scrie m

http://elth.srv.ro/

6
Prin alinierea spre dreapta s-a pus n evident structura de blocuri, adic ce
operatii fac obiectul unei comenzi de repetare sau de selectie (dac).
Aceast conventie nu este suficient de precis si poate fi nlocuit cu caractere
delimitator pentru operatiile dintr-un bloc ce face obiectul unei repetri sau
unei conditionri. Exemplu:
repet pentru fiecare ntreg m ntre 2 si n
{ s=0
repeta pentru fiecare ntreg d ntre 1 si m
{daca d este divizor al lui m atunci
aduna d la s
}
dac m = s atunci
scrie m
}

Un program este o descriere precis si concis a unui algoritm ntr-un limbaj


de programare. Un program are un caracter general si de aceea are nevoie de
date initiale (diferite de la o utilizare la alta a programului), date care
particularizeaz programul pentru o situatie concret. De exemplu, un program
pentru afisarea numerelor perfecte mai mici ca un numr dat n are ca date
initiale numrul n si ca rezultate numerele perfecte ntre 2 si n. Exemplu:
#include <stdio.h>
void main () {
int n,m,s,d ;
// declaratii de variabile
printf("n="); scanf("%d",&n); // citire date
for (m=2; m<=n ;m++) {
// repeta ptr fiecare m
s=0;
// suma divizorilor lui m
for (d=1; d<m ; d++) {
if ( m % d==0)
// restul mprtirii m/d
s=s+d;
// aduna un divizor la suma
}
if (m==s)
// daca m este numar perfect
printf ("\n %d", m); // afisare m singur pe o linie
}
}

Rezultatele produse de un program pe baza datelor initiale sunt de obicei


afisate pe ecran si/sau la imprimant. Datele se introduc manual de la tastatur
sau se citesc din fisiere disc.
Operatiile de prelucrare uzuale din limbajele de programare sunt operatii
aritmetice, operatii de comparatie si operatii de intrare-iesire (de citirescriere). Aceste operatii sunt exprimate prin instructiuni ale limbajului sau
prin apelarea unor functii standard predefinite (de bibliotec).

http://elth.srv.ro/

7
Desfsurarea n timp a operatiilor de prelucrare este controlat prin
instructiuni de repetere (de ciclare) si de selectie.
Fiecare limbaj de programare are reguli gramaticale precise, a cror
respectare este verificat de programul compilator (compilator = translator
dintr-un limbaj de programare universal n limbajul calculatorului pe care se
va executa programul).

Dezvoltarea de programe
Scrierea unui program ntr-un limbaj de programare este doar primul pas
dintr-un proces care mai cuprinde si alti pasi. Mai corect ar fi s spunem
scrierea unei versiuni initiale a programului, pentru c ntotdeauna aceast
form initial este corectat, modificat sau extins pentru eliminarea unor
erori, pentru satisfacerea unor noi cerinte sau pentru mbunttirea
performantelor n executie.
Un program scris ntr-un limbaj independent de masin (C, Pascal, s.a.)
trebuie mai nti tradus de ctre un program translator sau compilator.
Compilatorul citeste si analizeaz un text surs (de exemplu n limbajul C) si
produce un modul obiect (scris ntr-un fisier), dac nu s-au gsit erori n textul
surs. Pentru programele mari este uzual ca textul surs s fie format din mai
multe fisiere surs, care s poat fi scrise, compilate, verificate si modificate
separat de celelalte fisiere surs.
Mai multe module obiect, rezultate din compilri separate sunt legate
mpreun si cu alte module extrase din biblioteci de functii standard ntr-un
program executabil de ctre un program numit editor de legturi (Linker sau
Builder). Executia unui program poate pune n evident erori de logic sau
chiar erori de programare care au trecut de compilare (mai ales n limbajul C).
Cauzele erorilor la executie sau unor rezultate gresite nu sunt de obicei
evidente din cauz c ele sunt efectul unui numr mare de operatii efectuate de
calculator. Pentru descoperirea cauzelor erorilor se poate folosi un program
depanator (Debugger) sau se pot insera intructiuni de afisare a unor rezultate
intermediare n programul surs, pentru trasarea evolutiei programului.
Fazele de modificare (editare) a textului surs, de compilare, linkeditare si
executie sunt repetate de cte ori este necesar pentru a obtine un program
corect. De fapt, testarea unui program cu diverse date initiale poate arta
prezenta unor erori si nu absenta erorilor, iar efectuarea tuturor testelor
necesare nu este posibil pentru programe mai complexe (pentru. un
compilator sau un editor de texte, de exemplu).

http://elth.srv.ro/

8
Programele compilator si linkeditor pot fi apelate n mod linie de comand
sau prin selectarea unor optiuni din cadrul unui mediu integrat de dezvoltare a
programelor (IDE = Integrated Development Environment).
Alte programe utilizate n procesul de dezvoltare a unor aplicatii mari sunt:
- program bibliotecar pentru crearea si modificarea unor biblioteci de
subprograme pe baza unor module obiect rezultate din compilare.
- program pentru executia unor fisiere de comenzi necesare pentru compilarea
selectiv si re-crearea programului executabil, dup modificarea unor fisiere
surs sau obiect (make).
- program de control al versiunilor succesive de fisiere surs.

Llimbajul de programare C.
Limbajul C s-a impus n principal datorit existentei unui standard care
contine toate facilittile necesare unui limbaj pentru a putea fi folosit ntr-o
mare diversitate de aplicatii, fr a fi necesare abateri sau extinderi fat de
standard (cazul limbajului Pascal). Un exemplu este recunoasterea posibilittii
ca un program s fie format din mai multe fisiere surs si a compilrii lor
separate, inclusiv referiri dintr-un fisier n altul. In plus, exist un numr
relativ mare de functii uzuale care fac parte din standardul limbajului si care
contribuie la portabilitatea programelor C.
Unii programatori apreciaz faptul c limbajul C permite un control total
asupra operatiilor realizate de procesor si asupra functiilor sistemului de
operare gazd, aproape la fel ca si limbajele de asamblare. Astfel se explic de
ce majoritatea programelor de sistem si utilitare sunt scrise de mai multi ani n
limbajul C, pe lng multe programe de aplicatii.
Limbajul C permite scrierea unor programe foarte compacte, ceea ce poate fi
un avantaj dar si un dezavantaj, atunci cnd programele devin criptice si greu
de nteles. Scurtarea programelor C s-a obtinut prin reducerea numrului de
cuvinte cheie, prin existenta unui numr mare de operatori exprimati prin unul
sau prin dou caractere speciale dar si prin posibilitatea de a combina mai
multi operatori si expresii ntr-o singur instructiune (acolo unde alte limbaje
folosesc mai multe instructiuni pentru a obtine acelasi efect).
Din perspectiva timpului se poate spune c instructiunile C sunt o reusit a
limbajului (si au fost preluate fr modificari de multe alte limbaje : C++, Java
s.a.) dar functiile de intrare-iesire (printf,scanf) nu au fost un succes (si au fost
nlocuite n alte limbaje). Un alt neajuns s-a dovedit a fi necesitatea
argumentelor de tip pointer pentru functiile care trebuie s modifice o parte
din argumentele primite si a fost corectat prin argumente de tip referint.

http://elth.srv.ro/

9
Utilizarea direct de pointeri (adrese de memorie) de ctre programatorii C
corespunde lucrului cu adrese de memorie din limbajele de asamblare si
permite operatii imposibile n alte limbaje, dar n timp s-a dovedit si o surs
important de erori la executie, greu de depistat.
Au mai fost preluate n limbajele post-C si anumite conventii, cum ar fi
diferenta dintre litere mici si litere mari, diferenta dintre caractere individuale
si siruri de caractere (si terminarea sirurilor de caractere cu un octet zero),
operatorii, comentariile s.a.
Programarea n C este mai putin sigur ca n alte limbaje ( Pascal, Java) si
necesit mai mult atentie. Limbajul C permite o mare diversitate de
constructii corecte sintactic (care trec de compilare), dar multe din ele trdeaz
intentiile programatorului si produc erori greu de gsit la executie. Poate cel
mai bun exemplu este utilizarea gresit a operatorului de atribuire = n locul
operatorului de comparare la egalitate ==. Exemplu:
if ( a=b) printf (" a = b" \n");
if ( a==b) printf (" a = b" \n");
corect

// gresit
//

Elementele componente ale unui program


Orice limbaj de programare trebuie s contin:
- Instructiuni imperative, prin care se comand executarea anumitor actiuni
(prelucrri);
- Declaratii de variabile, de functii s.a., necesare compilatorului dar fr efect
la executie
- Comentarii, ignorate de compilator, destinate oamenilor care citesc
programe
In plus, limbajul C mai contine si directive preprocesor, pentru compilator.
Instructiunile executabile sunt grupate n functii (subprograme). In C trebuie
s existe cel putin o functie cu numele "main", cu care ncepe executia unui
program. Celelalte functii sunt apelate din functia "main" sau din alte functii
activate direct sau indirect de "main".
Prin "program" ntelegem uneori toate instructiunile necesare rezolvrii unei
probleme, deci o aplicatie complet, dar uneori se ntelege prin "program"
doar programul principal (functia "main"). Exemplu de program C minimal,
cu o functie "main" ce contine o singur instructiune (apelul functiei "printf")
si nu contine declaratii:
#include <stdio.h>
void main ( ) {

http://elth.srv.ro/

10
printf (" main ");
}

Cuvntul void reprezint tipul functiei "main" si arat c aceast functie nu


transmite nici un rezultat prin numele su. Parantezele care urmeaz
cuvntului "main" arat c numele "main" este numele unei functii (si nu este
numele unei variabile), dar o functie fr parametri. Sunt posibile si alte forme
de definire a functiei "main".
Acoladele sunt necesare pentru a delimita definitia unei functii, care este un
bloc de instructiuni si declaratii.
Un program descrie procedurile de obtinere a unor rezultate pe baza unor
date initiale si foloseste rezultate intermediare. Toate aceste date sunt
memorate n variabile ale programului. Pot exista si date constante, ale cror
valori nu se pot modifica n cursul executiei. Toate variabilele folosite ntr-un
program trebuie definite sau declarate prin declaratii ale limbajului. Exemplu:
#include <stdio.h>
/* calculeaza si afiseaza
void main ( ) {
int a,b; float c;
scanf ("%d%d", &a,&b);
c= (a+b)/2.0;
printf ("%f\n", c);
}

media a doua numere */


/*
/*
/*
/*

declaratii de variabile */
citire date initiale */
instructiune de calcul */
afisare rezultat */

In programul anterior "scanf" si "printf" sunt functii de citire de la tastatur


si respectiv de afisare pe ecran, iar liniile n care ele apar sunt instructiuni
pentru apelarea acestor functii. Practic nu exist program fr operatii de citire
a unor date si de scriere a unor rezultate. Datele initiale asigur adaptarea unui
program general la o problem concret iar rezultatele obtinute de program
trebuie comunicate persoanei care are nevoie de ele.
Un program este adresat unui calculator pentru a i se cere efectuarea unor
operatii, dar programul trebuie citit si nteles si de ctre oameni; de aceea se
folosesc comentarii care explic de ce se fac anumite operatii (comentariile
din exemplul anterior nu sunt un bun exemplu).
Initial n limbajul C a fost un singur tip de comentariu, care ncepea cu
secventa "/*' si se termina cu secventa "*/". Ulterior s-au adoptat si
comentariile din C++, care ncep cu secventa "//" si se termin la sfrsitul
liniei care contine acest comentariu, fiind mai comode pentru programatori.

Conventii lexicale ale limbajului C

http://elth.srv.ro/

11
Instructiunile si declaratiile limbajului C sunt formate din cuvinte cheie ale
limbajului, din nume simbolice alese de programator, din constante (numerice
si nenumerice) si din operatori formati n general din unul sau dou caractere
speciale.
Vocabularul limbajului contine litere mari si mici ale alfabetului englez,
cifre zecimale si o serie de caractere speciale, care nu sunt nici litere, nici
cifre. Printre caracterele speciale mult folosite sunt semne de punctuatie (',' ';'),
operatori ('=','+','-','*','/'), paranteze ('(',')',[',']','{'}') s.a.
In C se face diferent ntre litere mici si litere mari, iar cuvintele cheie ale
limbajului trebuie scrise cu litere mici. Cuvintele cheie se folosesc n declaratii
si instructiuni si nu pot fi folosite ca nume de variabile sau de functii (sunt
cuvinte rezervate ale limbajului). Exemple de cuvinte cheie:
int, float, char, void, unsigned,
do, while, for, if, switch
struct, typedef, const, sizeof

Numele de functii standard (scanf, printf, sqrt, etc.) nu sunt cuvinte cheie,
dar nu se recomand utilizarea lor n alte scopuri (schimbarea sensului initial,
atribuit n toate versiunile limbajului).
Literele mari se folosesc n numele unor constante simbolice predefinite :
EOF, M_PI, INT_MAX, INT_MIN

Prin numele de "spatii albe" se nteleg n C mai multe caractere folosite cu


rol de separator:
blanc ( ), tab ('\t'), linie nou ('\n'),
Acolo unde este permis un spatiu alb pot fi folosite oricte spatii albe (de
obicei blancuri).
Spatii albe sunt necesare ntre nume simbolice succesive (n declaratii, ntre
cuvinte cheie si/sau identificatori) dar pot fi folosite si ntre alti atomi lexicali
succesivi. Exemple:
const int * p;

typedef unsigned char byte;

Atomii lexicali ("tokens" n englez) sunt: cuvinte cheie, identificatori


(nume simbolice alese de programatori), numere (constante numerice),
constante sir (ntre ghilimele), operatori si separatori.
Un atom lexical trebuie scris integral pe o linie si nu se poate extinde pe mai
multe linii. In cadrul unui atom lexical nu se pot folosi spatii albe (cu exceptia
spatiilor dintr-un sir constant).

http://elth.srv.ro/

12
Respectarea acestei reguli poate fi mai dificil n cazul unor siruri constante
lungi, dar exist posibilitatea prelungirii unui sir constant de pe o linie pe alta
folosind caracterul '\'. Exemple:
puts (" Inceput sir foarte foarte lung\
sfrsit sir");
// spatiile albe se vor afisa
// solutie alternativa
puts ("Inceput sir foarte foarte lung",
"sfrsit sir");
// spatiile albe nu conteaz

Spatiile albe se folosesc n expresii pentru a usura citirea lor si la nceput de


linie pentru alinierea instructiunilor dintr-un bloc inclus ntr-o structur if,
while, for, do.

Structura programelor C.
Un program C este compus n general din mai multe functii, dintre care
functia "main" nu poate lipsi, deoarece cu ea ncepe executia programului.
Functiile pot face parte dintr-un singur fisier surs sau din mai multe fisiere
surs. Un fisier surs C este un fisier text care contine o succesiune de
declaratii: definitii de functii si, eventual, declaratii de variabile.
Functia main poate fi declarat fr argumente sau cu argumente, prin care
ea primeste date transmise de operator prin linia de comand care lanseaz
programul n executie. Functia main poate fi declarat si de tip int sau fr
tip explicit, dar atunci trebuie folosit instructiunea return pentru a preciza un
cod de terminare (zero pentru terminare normal, negativ pentru terminare cu
eroare). Exemplu:
#include
int main
printf
return
}

<stdio.h>
( ) {
(" main ");
0;

Definitia unei functii C are un antet si un bloc de instructiuni ncadrat de


acolade. In interiorul unei functii exist de obicei si alte blocuri de
instructiuni, ncadrate de acolade, si care pot contine declaratii de variabile.
Antetul contine tipul si numele functiei si o list de argumente.
Exemplu de program cu dou functii:
#include <stdio.h>
void clear () {
int i;

// sterge ecran prin defilare


// variabila locala functiei clear

http://elth.srv.ro/

13
for (i=0;i<24;i++)
putchar('\n');
}
void main ( ) {
clear( );
}

// apel functie

Functia clear putea fi scris (definit) si dup functia main, dar atunci
era necesar declararea acestei functii nainte de main. Exemplu:
#include <stdio.h>
void clear();
// declaratie functie
void main ( ) {
clear( );
// apel functie
}
void clear () {
// definitie functie
int i;
for (i=0;i<24;i++)
putchar('\n');
}

Intr-un program cu mai multe functii putem avea dou categorii de variabile:
- variabile definite n interiorul functiilor, numite si "locale".
- variabile definite n afara functiilor, numite si "externe" (globale).
Locul unde este definit o variabil determin domeniul de valabilitate al
variabilei respective: o variabil definit ntr-un bloc poate fi folosit numai n
blocul respectiv. Pot exista variabile cu acelasi nume n blocuri diferite; ele se
memoreaz la adrese diferite si se refer la valori diferite.
In primele versiuni ale limbajului C era obligatoriu ca toate declaratiile de
variabile dintr-o functie (sau dintr-un dintr-un bloc) s fie grupate la nceputul
functiei, nainte de prima instructiune executabil. In C++ si n ultimele
versiuni de C declaratiile pot apare oriunde, intercalate cu instructiuni.
Exemplu (incorect sintactic n C, corect sintactic n C++):
#include <stdio.h>
void main () {
int n;
scanf ("%d", &n);
long nf=1;
for (int k=1;k<=n;k++)
nf=nf*k;
printf ("%ld\n", nf);
}

//
//
//
//
//
//
//

calcul factorial
un ntreg dat
citeste valoare n
variabila rezultat
repeta de n ori
o nmultire
afisare rezultat

Directive preprocesor

http://elth.srv.ro/

14
Un program C contine una sau mai multe linii initiale, care ncep toate cu
caracterul #. Acestea sunt directive pentru preprocesorul C si sunt
interpretate nainte de a se analiza programul propriu-zis (instructiuni si
declaratii). Directivele fac parte din standardul limbajului C.
Cele mai folosite directive sunt #include si #define.
Directiva #include cere includerea n compilare a unor fisiere surs C, care
sunt de obicei fisiere antet (header), ce reunesc declaratii de functii
standard. Fisierele de tip .h nu sunt biblioteci de functii si nu contin definitii
de functii, asa cum se afirm uneori.
Pentru a permite compilatorului s verifice utilizarea corect a unei functii
este necesar ca el s afle declaratia functiei (sau definitia ei) nainte de prima
utilizare. Pentru o functie de bibiotec definitia functiei este deja compilat si
nu se mai transmite programului compilator, deci trebuie comunicate doar
informatiile despre tipul functiei, numrul si tipul argumentelor printr-o
declaratie (prototip al functiei). Fisierele antet contin declaratii de functii.
Absenta declaratiei unei functii utilizate (si datorit absentei unei directive
include) este semnalat ca avertisment n programele C si ca eroare ce nu
permite executia n C++. Pentru anumite functii absenta declaratiei afecteaz
rezultatul functiei (considerat implicit de tip int), dar pentru alte functii (de tip
void sau int) rezultatul nu este afectat de absenta declaratiei.
Orice program trebuie s citeasc anumite date initiale variabile si s scrie
(pe ecran sau la imprimant) rezultatele obtinute. In C nu exist instructiuni de
citire si de scriere, dar exist mai multe functii standard destinate acestor
operatii. Declaratiile functiilor standard de I/E sunt reunite n fisierul antet
stdio.h (Standard Input-Output ), care trebuie inclus n compilare:
#include <stdio.h>

Numele fisierelor antet pot fi scrise cu litere mici sau cu litere mari deoarece
nu sunt nume proprii limbajului C ci sunt nume specifice sistemului de
operare gazd (Windows, Linux etc.). Exemplu:
#include <STDIO.H>

Parantezele unghiulare < si > sunt delimitatori ai sirului de caractere ce


reprezint numele fisierului si arat c acest nume trebuie cutat ntr-un
anumit director (grup de fisiere). Numele unui fisier inclus poate fi delimitat si
de ghilimele atunci cnd el se afl n acelasi director cu programul care
contine directiva include. Exemplu:
#include stiva.h

Fiecare directiv de compilare trebuie scris pe o linie separat si nu trebuie


terminat cu caracterul ;, spre deosebire de instructiuni si declaratii.

http://elth.srv.ro/

15
In multe din exemplele care urmeaz vom considera implicite directivele de
includere necesare pentru functiile folosite, fr a le mai scrie (dar ele sunt
necesare pentru o compilare fr erori).

2. Date si prelucrri
Variabile si constante
Orice program prelucreaz un numr de date initiale si produce o serie de
rezultate. In plus, pot fi necesare date de lucru, pentru pstrarea unor valori
folosite n prelucrare, care nu sunt nici date initiale nici rezultate finale.
Toate aceste date sunt memorate la anumite adrese, dar programatorul se
refer la ele prin nume simbolice. Cu exceptia unor date constante, valorile
asociate unor nume se modific pe parcursul executiei programului. De aici
denumirea de variabile pentru numele atribuite datelor memorate.
Numele unei variabile ncepe obligatoriu cu o liter si poate fi urmat de
litere si cifre. Caracterul special _ (subliniere) este considerat liter, fiind
folosit n numele unor variabile sau constante predefinite (n fisiere de tip H).
Aplicatiile calculatoarelor sunt diverse, iar limbajele de programare reflect
aceast diversitate, prin existenta mai multor tipuri de date: tipuri numerice
ntregi si nentregi, siruri de caractere de lungime variabil s.a.
Pentru a preciza tipul unei variabile este necesar o definitie ( o declaratie).
Cuvintele definitie si declaratie se folosesc uneori cu acelasi sens,
pentru variabile declarate n main sau n alte functii.
In limbajul C se face diferent ntre notiunile de definitie si declaratie,
iar diferenta apare la variabile definite ntr-un fisier surs si declarate (si
folosite) ntr-un alt fisier surs. O definitie de variabil aloc memorie pentru
acea variabil (n functie de tipul ei) iar o declaratie anunt doar tipul unei
variabile definite n alt parte, pentru a permite compilatorului s verifice
utilizarea corect a variabilelor.
O declaratie trebuie s specifice numele variabilei (ales de programator),
tipul variabilei si, eventual, alte atribute. In C o variabil poate avea mai multe
atribute, care au valori implicite atunci cnd nu sunt specificate explicit (cu
exceptia tipului care trebuie declarat explicit).
O definitie de variabil poate fi nsotit de initializarea ei. Exemplu:
int suma=0;

// declaratie cu initializare

Tipuri de date n limbajul C

http://elth.srv.ro/

16

Principalele tipuri de date n C sunt:


- Tipuri numerice ntregi si nentregi, de diferite lungimi.
- Tipuri pointer (adrese de memorie)
- Tipuri structurate (derivate): vectori, structuri s.a.
Pentru functiile fr rezultat s-a introdus cuvntul void, cu sensul fr tip.
Tipul unei variabile C poate fi un tip predefinit (recunoscut de compilator) si
specificat printr-un cuvnt cheie (int,char,float etc) sau poate fi un nume de tip
atribuit de programator (prin declaratii typedef sau struct). Exemple de
declaratii de variabile:
int a,b;
float x,y,z; double d;
// tipuri standard
stiva s;
// tip definit de utilizator

Asemntor cu tipul variabilelor se declar si tipul functiilor. Exemple:


int cmmdc(int a, int b);
double sqrt (double x);

// declaratie (prototip)

Orice declaratie si orice instructiune trebuie terminat cu caracterul ;, dar


un bloc nu trebuie terminat cu ;. Exemplu de definire a unei functii simple:
double sqr (double x) { return x*x; }

// square

Declaratiile de variabile si de functii pot include si alte atribute: static,const.


Datorit reprezentrii interne complet diferite, limbajele de programare
trateaz diferit numerele ntregi si numerele reale, care pot avea o parte
fractionar. Pentru a utiliza eficient memoria si a satisface necesittile unei
multitudini de aplicatii exist n C mai multe tipuri de ntregi si respectiv de
reali, ce difer prin memoria alocat si deci prin numrul de cifre ce pot fi
memorate si prin domeniul de valori.
Implicit toate numerele ntregi sunt numere cu semn (algebrice), dar prin
folosirea cuvntului cheie unsigned la declararea lor se poate cere
interpretarea ca numere fr semn.
Tipurile ntregi sunt: char , short , int , long , long long
Tipuri nentregi: float , double , long double (numai cu semn).
Standardul C din 1999 prevede si tipul boolean _Bool (sau bool) pe un octet.
Reprezentarea intern si numrul de octeti necesari pentru fiecare tip nu sunt
reglementate de standardul limbajului C, dar limitele fiecrui tip pentru o
anumit implementare a limbajului pot fi aflate din fisierul antet limits.h.

http://elth.srv.ro/

17
Toate variabilele numerice de un anumit tip se reprezint pe acelasi numr
de octeti, iar acest numr limiteaz domeniul de valori (pentru ntregi si
nentregi) si precizia numerelor nentregi.
De exemplu, n Borland C , domeniul de valori pentru tipul int este cuprins
ntre -32767 si 32767 (ntregi cu semn pe 2 octeti) si de cca. 10 cifre
zecimale pentru tipul long). Depsirile la operatii cu ntregi de orice
lungime nu sunt semnalate desi rezultatele sunt incorecte n caz de depsire.
Exemplu:
short int a=15000, b=20000, c;
c=a+b;
// depasire ! c > 32767

Reprezentarea numerelor reale n diferite versiuni ale limbajului C este mai


uniform deoarece urmeaz un standard IEEE de reprezentare n virgul
mobil. Pentru tipul float domeniul de valori este ntre 10E-38 si 10E+38 iar
precizia este de 6 cifre zecimale exacte. Pentru tipul double domeniul de valori
este ntre 10E-308 si 10E+308 iar precizia este de 15 cifre zecimale.
De observat c, la afisarea valorilor unor variabile reale se pot cere mai
multe cifre zecimale dect pot fi memorate, dar cifrele suplimentare nu sunt
corecte. Se pot cere, prin formatul de afisare, si mai putine cifre zecimale
dect sunt memorate n calculator.

Constante n limbajul C
Tipul constantelor C rezult din forma lor de scriere, dup cum urmeaz:
- Constantele ntregi sunt siruri de cifre zecimale, eventual precedate de un
semn (-, +). Exemple :
0 , 11 , -205 , 12345

- Constantele care contin, pe lng cifre si semn, un punct zecimal si/sau


litera E (sau e) sunt de tipul double. Exemple:
7.0 , -2. , 0.5 ,

.25 , 3e10 , 0.12345678E-14

- Constantele care contin un exponent precedat de litera E (e) sau contin


un punct zecimal dar sunt urmate de litera F (f) sunt de tipul float.
Exemple:
1.0f, -2.F , 5e10f , 7.5 E-14F

- Constantele formate dintr-un caracter ntre apostrofuri sunt de tip char.


Exemple:
0, a , A, +, -, \n , \t,

http://elth.srv.ro/

18
Constantele caracter se pot scrie si sub forma unei secvente ce ncepe cu \,
urmat de o liter (\n = new line , \t =tab , \b = backspace etc), sau de codul
numeric al caracterului n octal sau n hexazecimal (\012 = \0x0a = 10 este
codul pentru caracterul de trecere la linie nou \n).
- Constantele ntregi n baza 16 trebuie precedate de precedate de sufixul
"0x". Cifrele hexazecimale sunt 0..9,A,B,C,D,E,F sau 0..9,a,b,c,d,e,f. Exemple
0x0A, 0x7FFFF, 0x2c, 0xef
- Constantele formate din unul sau mai multe caractere ntre ghilimele sunt
constante sir de caractere . Exemple:
a , alfa , -1234 ,

####

Orice constant poate primi un nume, devenind o constant simboplic.


Utilizarea de constante simbolice n programe are mai multe avantaje:
- Permit modificarea mai simpl si mai sigur a unei constante care apare n
mai multe locuri.
- Permite ntelegerea mai usoar a programelor, cu mai putine comentarii.
Exemplu de nume pentru constanta ce reprezint dimensiunea maxim a
unor vectori :
#define NMAX 1000
// dimensiune maxima
void main () {
int n, x[NMAX], y[NMAX];
printf ("n= "); scanf ("%d" &n);
assert ( n < NMAX);
... // citire elemente vectori

Declaratia enum permite definirea mai multor constante ntregi cu valori


succesive, simultan cu definirea unui nou tip de date. Exemplu:
enum color {BLACK,BLUE,RED}; //BLACK=0,BLUE=1,RED=2

Se poate atribui explicit constantelor o valoare diferit de zero. Exemplu:


enum color{RED=5,WHITE=15,BLUE=1};

Operatori si expresii aritmetice n limbajul C


O expresie este format din operatori, operanzi si paranteze rotunde.
Operanzii pot fi constante, variabile sau functii. Parantezele se folosesc pentru
a delimita subexpresii, care se calculeaz naintea altor subexpresii, deci
pentru a impune ordinea de calcul. Exemple de expresii aritmetice:
5, x , k+1 , a/b, a/(b*c), 2*n-1 , 1./sqrt(x)

http://elth.srv.ro/

19
Operatorii aritmetici +,-,*, / se pot folosi cu operanzi numerici ntregi
sau reali.
Operatorul / cu operanzi ntregi are rezultat ntreg (partea ntreag a
ctului) si operatorul % are ca rezultat restul mprtirii ntregi a doi ntregi.
Semnul restului este acelasi cu semnul demprtitului; restul poate fi negativ.
In general, rezultatul unei (sub)expresii cu operanzi ntregi este ntreg.
Dac cei doi operanzi difer ca tip atunci tipul inferior este automat
promovat la tipul superior nainte de efectuarea operatiei. Un tip T1 este
superior unui tip T2 dac toate valorile de tipul T2 pot fi reprezentate n tipul
T1 fr trunchiere sau pierdere de precizie. Ierarhia tipurilor aritmetice din C
este urmtoarea:
char < short < int < long < float < double < long double
Subexpresiile cu operanzi ntregi dintr-o expresie care contine si reali au
rezultat ntreg, deoarece evaluarea subexpresiilor se face n etape. Exemple:
float x = 9.8, y = 1/2*x;
y= x/2; // y=4.9;

// y=0.

In limbajul C exist operator de atribuire =, iar rezultatul expresiei de


atribuire este valoarea atribuit (copiat). In partea stng a unei atribuiri se
poate afla o variabil sau o expresie de indirectare printr-un pointer; n partea
dreapt a operatorului de atribuire poate sta orice expresie. Exemple:
k=1;

i=j=k=0;

d = b*b-4*a*c;

x1=(-b +sqrt(d))/(2*a);

La atribuire, dac tipul prtii stnga difer de tipul prtii dreapta atunci se
face automat conversia de tip (la tipul din stnga), chiar dac ea necesit
trunchiere sau pierdere de precizie. Exemplu:
int a;

a= sqrt(3.);

// a=1

Exemplu de conversii dorite de programator:


float rad,grd,min;
int g,m;
minute
grd = 180*rad/M_PI;
g=grd;
min=60*(grd-(float)g);
m=min;

// radiani, grade, minute


//
nr
intreg
de
grade,
// sau g= (int)grd;
// min=60*(grd-g)
// sau m= (int)min;

Conversiile automate pot fi o surs de erori (la executie) si de aceea se


prefer conversii explicite prin operatorul de conversie (cast= fortare tip),
care are forma (tip) si se aplica unei expresii. Exemple:

http://elth.srv.ro/

20

float x; int a,b;


x= (float)a/b;
float x; int k;
k= (int)(x+0.5);

// ctul exact
// rotunjire x la intregul apropiat

Conversia prin operatorul (tip) se poate face ntre orice tipuri aritmetice sau
ntre tipuri pointer. Pentru tipurile aritmetice se poate folosi si atribuirea
pentru modificarea tipului (si valorii) unor variabile sau functii. Exemplu:
float x; int k;
x= x+0.5; k=x;

// rotunjire x

In limbajul C exist mai multi operatori care reunesc un calcul sau alt
prelucrare cu o atribuire. Exemple:
+=
-=
*=
/= %=
Efectul unei expresii de forma
v += e

este echivalent cu efectul expresiei


v = v + e

unde v este o variabil, iar e este o expresie.


Operatorii unari de incrementare (++) si decrementare (--) au ca efect
mrirea si respectiv micsorarea cu 1 a valorii operandului numeric:
++x adun 1 la x nainte de se folosi valoarea variabilei x
x++ adun 1 la x dup ce se foloseste valoarea variabilei x
Operatorii ++ si -- se pot aplica oricrei expresii numerice (ntregi sau reale)
si variabilelor pointer. In general acesti operatori realizeaz o prescurtare a
atribuirilor de forma x=x+1 sau x=x-1, dar pot exista si diferente ntre cele
dou forme de mrire sau diminuare a unei valori. De exemplu ( a= ++b )
are alt rezultat dect ( a=b++ ).
Urmtoarele expresii au acelasi efect dac x este o variabil :
x=x+1

x += 1

++x

x++

Erori de reprezentare a numerelor


In aplicatiile numerice pot apare o serie de erori datorit reprezentrii
numerelor n calculatoare si particularittilor operatorilor aritmetici:
- Erori la mprtire de ntregi si la atribuire la un ntreg. Exemple:
x = 1/2*(a+b);

// x=0,

corect:

x=(a+b)/2 ;

http://elth.srv.ro/

21
int x = sqrt(2);

// x=1

- Erori de depsire a valorilor maxime absolute la operatii cu ntregi, chiar si


n valori intermediare (n subexpresii). Un exemplu este calculul numrului de
secunde fat de ora zero pe baza a trei ntregi ce reprezint ora, minutul si
secunda. Acest numr poate depsi cel mai mare ntreg reprezentabil pe 16 biti
(short sau int n unele implementri). Exemplu:
#include <stdio.h>
// interval intre doua momente de timp
void main () {
int h1,m1,s1, h2,m2,s2, h,m,s;
long t1,t2,t; int r;
printf("timp1="); scanf("%d%d%d",&h1,&m1,&s1);
printf("timp2="); scanf("%d%d%d",&h2,&m2,&s2);
t1= 3600L*h1 + 60*m1 + s1;
// poate depasi daca t1
int
t2= 3600L*h2 + 60*m2 + s2;
// poate depasi daca t2
int
t=t1-t2;
h= t/3600;
r=t%3600;
m=r/60;
s=r%60;
printf ("%02d:%02d:%02d \n",h,m,s);
}

Nu exist nici o metod general de a detecta depsirile la operatii cu ntregi


pe un numr mare de calcule, dar n cazuri simple putem s verificm
rezultatul unei operatii unde suspectm o depsire. Exemplu:
void main () {
int a,b,c;
scanf ("%d%d",&a,&b);
c=a*b;
if ( c/a != b)
printf ("depasire !\n");
else
printf ("%d \n",c);
}

O alternativ este prevenirea aceste depsiri. Exemplu:


if (MAXINT /a < b)
// MAXINT definit in <values.h>
printf ("depasire ! \n");
else
printf ("%d \n", a*b);

http://elth.srv.ro/

22
- Erori la adunarea sau scderea a dou numere reale cu valori foarte diferite
prin aducerea lor la acelasi exponent nainte de operatie. Se poate pierde din
precizia numrului mai mic sau chiar ca acesta s fie asimilat cu zero.
- Erori de rotunjire a numerelor reale datorit numrului limitat de cifre
pentru mantis. Mrimea acestor erori depinde de tipul numerelor (float sau
double sau long double), de tipul, numrul si ordinea operatiilor aritmetice.
Pierderea de precizie este mai mare la mprtire si de aceea se recomand ca
aceste operatii s se efectueze ct mai trziu ntr-o secvent de operatii. Deci:
expresia (a*b)/c este preferabil expresiei (a/c)*b.
Erorile de rotunjire se pot cumula pe un numr mare de operatii, astfel c n
anumite metode iterative cresterea numrului de pasi (de iteratii) peste un
anumit prag nu mai reduce erorile de calcul intrinseci metodei, deoarece
erorile de reprezentare nsumate au o influent prea mare asupra rezultatelor.
Un exemplu este calculul valorii unor functii ca sum a unei serii de puteri cu
multi termeni; ridicarea la putere si factorialul au o crestere rapid pentru
numere supraunitare iar numerele subunitare ridicate la putere pot produce
valori nesemnificative.
In general, precizia rezultatelor numerice este determinat de mai multi
factori: precizia datelor initiale (numr de zecimale), numrul si felul
operatiilor, erori intrinseci metodei de calcul (pentru metode de aproximatii
succesive), tipul variabilelor folosite.

Prelucrri la nivel de bit


O variabil este un nume pentru o zon de memorie, care contine un sir de
cifre binare (biti). Operatorii aritmetici interpreteaz sirurile de biti ca numere
binare cu semn. Anumite aplicatii dau alte interpretri sirurilor de biti si
necesit operatii la nivel de bit sau grupuri de biti care nu sunt multiplii de 8.
Operatorii la nivel de bit din C sunt aplicabili numai unor operanzi de tip
ntreg. Putem deosebi dou categorii de operatori pe biti:
- Operatori logici bit cu bit
- Operatori pentru deplasare cu un numr de biti
Operatorul unar '~' face o inversare logic bit cu bit a operandului si poate fi
util n crearea unor configuratii binare cu multi biti egali cu 1, pe orice
lungime. Exemplu:
~0x8000

// este 0x7FFF

Operatorul pentru produs logic bit cu bit '&' se foloseste pentru fortarea pe
zero a unor biti selectati printr-o masc si pentru extragerea unor grupuri de

http://elth.srv.ro/

23
biti dintr-un sir de biti. Pentru a extrage cei 4 biti din dreapta (mai putini
semnificativi) dintr-un octet memorat n variabila 'c' vom scrie:
c & 0x0F

unde constanta hexa 0x0F reprezint un octet cu primii 4 biti zero si ultimii 4
biti egali cu 1.
Operatorul pentru sum logic bit cu bit '|' se foloseste pentru a forta selectiv
pe 1 anumiti biti si pentru a reuni dou configuratii binare ntr-un singur sir de
biti. Exemplu:
a|0x8000

// pune semn minus la numarul din a

Operatorul pentru sum modulo 2 ("sau exclusiv") '^' poate fi folosit pentru
inversarea logic sau pentru anularea unei configuratii binare.
Operatorii pentru deplasare stnga '<<' sau dreapta '>>' se folosesc pentru
modificarea unor configuratii binare. Pentru numere fr semn au acelasi efect
cu nmultirea si respectiv mprtirea cu puteri ale lui 2. Exemplu:
a >>10

// echivalent cu a / 1024

Functia urmtoare afiseaz prin 4 cifre hexa un sir de 16 biti primit ca


parametru :
void printHex ( unsigned short h) {
unsigned short i, ch;
for (i=1;i<=4;i++) {
ch= h & 0xF000; // extrage primii 4 biti din stanga
h = h << 4;
// se aduce urmatorul grup de 4
biti
// scrie ca cifra hexa ch aliniat la dreapta
printf ("%01x",ch>>12);
}
}

Ordinea de evaluare a expresiilor


Limbajul C are un numr mare de operatori care pot fi combinati n expresii
complexe. Ordinea n care actioneaz acesti operatori ntr-o expresie este dat
n urmtorul tabel de prioritti:
Prioritate
1
2
3
4
5

Operator
Paranteze si acces la structuri: ( ) [ ] -> .
Operatori unari: ! ~ + - ++ -- & * sizeof (tip)
Inmultire, mprtire, rest : * / %
Adunare si scdere: + Deplasri: << >>

http://elth.srv.ro/

24
6
7
8
9
10
11
12
13
14
15

Relatii: <= < > >=


Egalitate: == !=
Produs logic bit cu bit: &
Sau exclusiv bit cu bit: ^
Sum logic bit cu bit: |
Produs logic: &&
Sum logic: ||
Operator conditional: ? :
Atribuiri: = *= /= %= += -= &= ^= |= <<= >>=
Operator virgula: ,

Ignorarea priorittii operatorilor conduce la erori de calcul detectabile numai


la executie, prin depanarea programului.
Dou recomandri utile sunt evitarea expresiilor complexe (prin folosirea de
variabile pentru rezultatul unor subexpresii) si utilizarea de paranteze pentru
specificarea ordinii de calcul (chiar si atunci cnd ele nu sunt necesare).
Operatorii de aceeasi prioritate se evalueaz n general de la stnga la
dreapta, cu exceptia unor operatori care actioneaz de la dreapta la stnga
(atribuire, operatorii unari si cel conditional).
Operatorii unari actioneaz naintea operatorilor binari. Intre operatorii
binari sunt de retinut cteva observatii:
- Operatorul de atribuire simpl si operatorii de atribuire combinat cu alte
operatii au prioritate foarte mic (doar operatorul virgul are prioritate mai
mic); de aceea pot fi necesare paranteze la subexpresii de atribuire din
componenta altor expresii. Exemple n care atribuirea trebuie efectuat nainte
de a compara valoarea atribuit:
while ( (c =getchar()) != EOF) ...
if ( (d= b*b-4*a*c) < 0) ...

- Operatorii aritmetici au prioritate naintea celorlalti operatori binari, iar


operatorii de relatie au prioritate fat de operatorii logici. Exemplu:
(a<<3) + (a<<1)

// a*10 = a*8 + a*2

Instructiuni expresie n C
O expresie urmat de caracterul ; devine o instructiune expresie. Cazurile
uzuale de instructiuni expresie sunt :
- Apelul unei functii (de tip void sau de alt tip) printr-o instructiune :
printf("n="); scanf("%d",&n);

http://elth.srv.ro/

25
- Instructiune de atribuire:
a=1;

b=a;

r=sqrt(a);

c=r/(a+b);

i=j=k=1;

- Instructiune vid (expresie nul):


;

- Instructiuni fr echivalent n alte limbaje:


++a;

a++;

a<<2;

Prin instructiuni expresie se exprim operatiile de prelucrare si de intrareiesire necesare oricrui program.
Exemplu de program compus numai din instructiuni expresie:
#include <stdio.h>
#include <math.h>
void main () {
float a,b,c,ua,ub,uc;
printf("Lungimi laturi:");
scanf ("%f%f%f",&a,&b,&c);
ua = acos ( (b*b+c*c-a*a)/(2*b*c)
ub = acos ( (a*a+c*c-b*b)/(2*a*c)
uc = acos ( (b*b+a*a-c*c)/(2*a*b)
printf ("%8.6f%8.6f \n",ua+ub+uc,
}

); //
); //
); //
M_PI);

unghi A
unghi B
unghi C
// verificare

O declaratie cu initializare seamn cu o instructiune de atribuire, dar ntre


ele exist cel putin dou diferente:
- O declaratie poate apare n afara unei functii, dar o instructiune nu poate fi
scris dect ntr-o functie.
- O declaratie nu poate apare ntr-o structur if , for, while, do. Exemplu:
while (int r=a%b) ... // eroare sintactic

Functii standard de intrare-iesire


Functiile scanf si printf permit citirea cu format (ales de programator) si
scrierea cu format pentru orice tip de date. Pentru numere se face o conversie
automat ntre formatul extern (sir de caractere care sunt de obicei cifre
zecimale) si formatul intern (binar virgul fix sau virgul mobil). Primul
argument al functiilor scanf si printf este un sir de caractere ce poate
contine:
- specificatori de format, adic secvente de caractere care ncep cu %.
- alte caractere, afisate ca atare de printf

http://elth.srv.ro/

26
Celelate argumente sunt variabile (la scanf) sau expresii (la printf) n
care se citesc valori (scanf) sau ale cror valori se scriu (printf). Exemple
de utilizare printf:
printf
printf
printf
linia
printf
printf

("\n");
// trecere la o noua linie
("\n Eroare \n"); // scrie un sir constant
("%d \n",a);
// scrie un intreg si schimba
("a=%d b=%d \n", a, b); // scrie doi intregi
( %2d grade %2d min %2d sec \n, g,m,s);

Argumentele functiei scanf sunt de tip pointer si contin adresele unde se


memoreaz valorile citite. De obicei aceste adrese se obtin cu operatorul de
adresare (&) aplicat variabilei care primeste valoarea citit. Exemple:
scanf("%d",&n);
// citeste un ntreg n variabila n
scanf("%d%d", &a,&b); // citeste doi ntregi in a si b
scanf (%f, &rad);
// citeste un numar real in rad

De retinut diferenta de utilizare a functiilor scanf si printf. Exemplu:


scanf("%d%d", &a,&b); // citeste numere in a si b
printf("%d %d", a,b); // scrie valorile din a si b

Numerele citite cu scanf pot fi introduse pe linii separate sau n aceeasi


linie dar separate prin spatii albe sau caractere Tab. Intre numere succesive
pot fi oricte caractere separator (\n,\t, ). Un numr se termin la primul
caracter care nu poate apare ntr-un numr .
Functiile scanf si printf folosesc notiunea de cmp (field): un cmp
contine o valoare si este separat de alte cmpuri prin spatii albe, inclusiv
terminator de linie (\n) ca spatiu alb. Fiecare descriptor de format poate
contine mrimea cmpului, ca numr ntreg. Aceast mrime se foloseste mai
ales la afisare, pentru afisare numere pe coloane, aliniate la dreapta. In lipsa
acestei informatii mrimea cmpului rezult din valoarea afisat. Exemple:
printf("%d %d",a,b); // 2 campuri separate prin blanc
printf("%8d8%d",a,b); // 2 cmpuri de cate 8 caractere

Desi sunt permise si alte caractere n sirul cu rol de format din scanf se
recomand pentru nceput s nu se foloseasc ntre specificatorii de format
dect blancuri (pentru a usura ntelegerea formatului de citire).
Functia scanf nu poate afisa nimic, iar pentru a precede introducerea de
date de un mesaj trebuie folosit secventa printf, scanf. Exemplu:

http://elth.srv.ro/

27
printf (n= ); scanf (%d, &n);

Specificatorii de format pentru citirea si scrierea de numere n baza 10 sunt:


%d , %i
%hd, %hi
%ld, %li
%u
%f , %e , %g
%lf ,%le, %lg
%Lf ,%Le, %Lg

numere
numere
numere
numere
numere
numere
numere

ntregi cu semn int


ntregi scurte short
ntregi lungi long
ntregi fara semn
reale de tip float
reale de tip double
reale de tip long double

3. Prelucrri conditionate

Structuri de control
Instructiunile de control dintr-un limbaj permit selectarea si controlul
succesiunii n timp a operatiilor de prelucrare. In limbajele masin si n
primele limbaje de programare controlul succesiunii se realiza prin
instructiuni de salt n program (instructiunea go to mai exist si n prezent n C
si n alte limbaje, desi nu se recomand utilizarea ei).
S-a demonstrat teoretic si practic c orice algoritm (program) poate fi
exprimat prin combinarea a trei structuri de control:
- succesiune fix de operatii (secventa liniar)
- decizie binar (alegere dintre dou alternative posibile)
- ciclul cu conditie initial (repetarea unor operatii n functie de o conditie)
Limbajul C este un limbaj de programare structurat deoarece posed
instructiuni pentru exprimarea direct a acestor trei structuri de control, fr a
se mai folosi instructiuni de salt.
Combinarea celor trei structuri se face prin includere; orice combinatie este
posibil si pe oricte niveluri de adncime (de includere). Deci un ciclu poate
contine o secvent sau o decizie sau un alt ciclu, s.a.m.d.
Limbajul C contine si alte structuri de control, pe lng cele strict necesare:
- selectie multipl (dintre mai multe alternative)
- ciclul cu conditie final (verificat dup executarea operatiilor din ciclu)
- ciclul for (cu conditie initial sau cu numr cunoscut de pasi)

Blocul de instructiuni

http://elth.srv.ro/

28
Instructiunile expresie dintr-un program sunt executate n ordinea aparitiei
lor n program, deci secventa liniar de operatii este realizat natural, prin
ordinea n care sunt scrise instructiunile ntr-un program.
In limbajul C un bloc grupeaz mai multe instructiuni (si declaratii) ntre
acolade. Exemple:
{ t=a; a=b; b=t;}
{ int t; t=a; a=b; b=t;}

// schimba a si b ntre ele


// schimba a si b prin t

Uneori un bloc contine doar o singur instructiune. Un bloc nu trebuie


terminat cu ;.
Acoladele nu modific ordinea de executie, dar permit tratarea unui grup de
instructiuni ca o singur instructiune de ctre alte instructiuni de control (if,
while, do, for s.a). Instructiunile de control au ca obiect, prin definitie, o
singur instructiune (care se repet sau care este selectat pentru executie).
Pentru a extinde domeniul de actiune al acestor instructiuni la un grup de
operatii se folosesc acolade pentru gruparea instructiunilor vizate de
comenzile if, for,while,do, switch. Exemplu:
scanf (%d, &n);
if ( n > MAX) {
printf (Eroare in date: n > %d \n,MAX);
return;
}

Instructiunea "if"
Instructiunea introdus prin cuvntul cheie "if" exprim o decizie binar si
poate avea dou forme: o form fr cuvntul else si o form cu else :
if (e) i
if (e) i1 else i2

// fara alternativa else


// cu alternativa else

In descrierea unor structuri de control vom folosi urmtoarele notatii:


e, e1, e2,...
expresii (sau conditii)
i, i1, i2
instructiuni sau blocuri
Instructiunile i, i1,i2 pot fi:
- O instructiune simpl, terminat cu ';' (terminatorul face parte din
instructiune).
- O instructiune compus, ntre acolade.
- O alt instructiune de control.

http://elth.srv.ro/

29
Expresia din if este de obicei o expresie de relatie sau o expresie logic, dar
poate fi orice expresie cu rezultat numeric.
Exemplu de instructiune if fr alternativ else:
// maxim dintre a si b
max=a;
if ( max < b)
max=b;
printf ("%d \n", max);

Valoarea expresiei dintre paranteze se compar cu zero, iar instructiunea


care urmeaz se va executa numai atunci cnd expresia are o valoare nenul.
In general expresia din instructiunea if reprezint o conditie, care poate fi
adevarat (valoare nenul) sau fals (valoare nul). De obicei expresia este o
expresie de relatie (o comparatie de valori numerice) sau o expresie logic
care combin mai multe relatii ntr-o conditie compus.
De multe ori alegerea se face ntre dou secvente de operatii (instructiuni) si
trebuie folosite acoladele pentru precizarea acestor secvente. Exemplu:
// inversarea valorilor lui a si b daca a>b
if ( a > b) {
t=a; a=b; b=t;
}

De observat c pentru comparatia la diferit de zero nu trebuie neaprat


folosit operatorul de inegalitate (!=), desi folosirea lui poate face programul
mai clar:
if (d) return;

//

if (d != 0) return;

Forma instructiunii if care foloseste cuvntul cheie else permite alegerea


dintre dou secvente de operatii posibile, n functie de o conditie. Exemplu:
// determinare minim dintre a si b
if ( a < b)
min=a;
else
min=b;

Instructiunile precedate de if si else sunt de obicei scrise pe liniile urmtoare


si sunt deplasate spre dreapta, pentru a pune n evident structurile si modul de
asociere ntre if si else. Acest mod de scriere permite citirea corect a unor
cascade de decizii. Exemplu:
// determinare tip triunghi cu laturile a,b,c

http://elth.srv.ro/

30
if ( a==b && b==c)
printf ("echilateral \n");
else
if ( a==b || b==c || a==c)
printf ("isoscel \n");
else
printf ("oarecare \n");

O problem de interpretare poate apare n cazul a dou (sau mai multe)


instructiuni if incluse, dintre care unele au alternativa else, iar altele nu contin
pe else. Regula de interpretare este aceea c else este asociat cu cel mai
apropiat if fr else (dinaintea lui). Exemplu:
if ( a == b )
if (b == c)
printf ("a==b==c \n");
else
printf (" a==b si b!=c \n");

Pentru a programa o instructiune if cu else care contine un if fr else avem


mai multe posibilitti:
if ( e1) {
if (e2)
i1
}
else
i2

if ( ! e1)
i2
else if (e2)
i1

Exemplu dintr-un program care inverseaz pe a cu b daca a<b:


if ( a>0 && b>0) {
if ( a<b ) {
c=a; a=b; b=c;
}
}
else {
printf (eroare in date \n);
return;
}

O solutie mai simpl si mai clar este urmtoarea:


if ( a <= 0 || b <= 0) {
printf (eroare in date \n);
return;
}

http://elth.srv.ro/

31
if ( a<b ) {
c=a; a=b; b=c;
}

Din exemplele anterioare se vede c modul de exprimare a conditiilor


verificate si ordinea lor poate simplifica sau poate complica inutil un program.

Operatori de relatie si logici


Operatorii de relatie se folosesc de obicei ntre operanzi numerici si, mai rar,
ntre variabile pointer. In limbajul C operatorii de comparatie la egalitate si
inegalitate arat mai deosebit:
== comparatie la egalitate (identitate)
!= comparatie la inegalitate
Operatorii pentru alte relatii au forma din matematic si din alte limbaje:
< , <= , >, >=
Toti operatorii de relatie au rezultat zero (0) dac relatia nu este adevrat si
unu (1) dac relatia este adevrat.
Comparatia la egalitate de numere nentregi este nesigur si trebuie evitat,
din cauza erorilor de reprezentare intern a numerelor reale. Se va compara
mai bine diferenta celor dou valori cu un epsilon foarte mic. Exemplu:
// daca punctul (x0,y0) se afla pe dreapta y=a*x+b
if ( fabs (y0- (a*x0+b)) < 1e-5) ...
// in loc de if ( y0 ==a*x0+b) ...

Operatorii logici se folosesc de obicei ntre expresii de relatie pentru a


exprima conditii compuse din dou sau mai multe relatii. Operatorii logici au
rezultat 1 sau 0 dup cum rezultatul expresiei logice este adevrat sau fals.
Operatorii logici binari n C sunt:
&&
||

si-logic
sau-logic

( a && b =1 dac si a==1 si b==1)


( a || b =1 dac sau a==1 sau b==1 sau a==b==1)

Operatorul && se foloseste pentru a verifica ndeplinirea simultan a dou


sau mai multe conditii, iar operatorul || se foloseste pentru a verifica dac cel
putin una dintre dou (sau mai multe) conditii este adevrat.
Exemple de conditii compuse:

http://elth.srv.ro/

32
if ( x >= a && x <= b)
printf(" x in [a,b] \n");
if ( x < a || x > b)
printf ("x in afara interv. [a,b] \n");

De observat c efectuarea mai multor verificri poate fi exprimat uneori fie


prin mai multe instructiuni if, fie printr-o singur instructiune if cu expresie
logic. Exemplu:
if ( x >= a)
if ( x <= b)
printf(" x in [a,b] \n");

Diferenta apare atunci cnd exist alternative la fiecare conditie testat.


if ( x >= a)
if ( x <= b)
printf(" x intre a si b \n");
else
printf(" x > b \n");
else
printf(" x < a\n");

Operatorul unar de negare logic este '!'. Exemplu:


if (!d) return;

// if ( d==0) return;

Negarea unei sume logice este un produs logic si reciproc. Exemple:


a >=0 && b >=0
x < a || x > b

// echiv. cu
// echiv. cu

!(a<0 || b<0)
!(x>=a && x<=b)

Intotdeauna putem alege ntre testarea unei conditii sau a negatiei sale, dar
consecintele acestei alegeri pot fi diferite, ca numr de instructiuni, mai ales
atunci cnd instructiunea if se afl ntr-un ciclu. Exemplu:
// determina minim dintr-un vector x
xmin=x[0];
// minim partial
for (i=1;i<n;i++)
if (xmin <= x[i])
;
// nimic daca xmin este minim
else
xmin=x[i];
// modifica minim partial

Varianta preferat este:


// determina minim dintr-un vector x

http://elth.srv.ro/

33
xmin=x[0];
for (i=1;i<n;i++)
if (xmin > x[i])
xmin=x[i];

Prioritatea operatorilor logici este mai mic dect a operatorilor de relatie si


de aceea nu sunt necesare paranteze n jurul expresiilor de relatie combinate
prin operatori logici. Exemplu:
// verifica daca a,b,c pot fi laturile unui triunghi
if (a < b+c && b < a+c && c < a+b)
printf ("a,b,c pot forma un triunghi \n");
// verifica daca a,b,c nu pot fi laturile unui
triunghi
if ( a > b+c || b > a+c || c > a+b )
printf (" a,b,c nu pot forma un triunghi \n");

Intr-o expresie logic evaluarea operanzilor (expresii de relatie) se face de la


stnga la dreapta; din acest motiv ordinea operanzilor ntr-o expresie logic
poate fi uneori important si poate conduce la erori de programare. Exemplu:
void main () {
int k, b=9, a[]={1,2,3,4};
k=0;
while ( b != a[k] && k<5 )
k++;
if (k<5)
printf ("gasit in pozitia %d \n",k);
else
printf ("negasit \n");
}

In programul anterior indicele k poate ajunge egal cu 4 iar, n anumite


implementri (Borland C, de ex.) rezultatul afisat este gasit n pozitia 4
deoarece valoarea lui b este memorat imediat lng a[3]. In astfel de cazuri
trebuie verificat mai nti dac variabila k este n domeniul permis si apoi s
fie folosit n comparatie:
while ( k < 5 && b != a[k] )
k++;

Dac primul operand dintr-o expresie logic determin rezultatul expresiei


(prin valoarea sa) nu se mai evalueaz si ceilalti operanzi (n expresii de
relatie care pot include si calcule). Evaluarea unui produs logic se opreste la
primul operand nul, deoarece este sigur c rezultatul produsului va fi nul

http://elth.srv.ro/

34
(fals), indiferent de valorile celorlalti operanzi. La fel, evaluarea unei sume
logice se opreste la primul operand nenul, cu rezultat 1 (adevrat).
Pentru a preveni erori cauzate de acest mod de evaluare se vor evita expresii
complicate care includ calcule, atribuiri si verificri de conditii.

Expresii conditionale
Limbajul C contine o expresie ternar (cu trei operanzi), care poate fi privit
ca o expresie concentrat a unei instructiuni if:
exp1 ? exp2 : exp3
Instructiunea
urmtoare:

x =e1?e2:e3 este echivalent ca efect cu instructiunea

if (e1) x=e2;
else x=e3;

Diferenta este c expresia conditional nu necesit o variabil care s


primeasc rezultatul (exp2 sau exp3) si poate reduce lungimea unor secvente
de program sau unor functii . Exemple:
// functie pentru minim intre doua variabile
int minim (int a, int b) {
return a<b ? a:b;
}
// afisarea unui mesaj dintre 2 posibile
printf ( prim ? "este prim \n" : "nu este prim \n");

Uneori se poate reduce numrul de instructiuni if fr expresii conditionale,


dar folosind alte observatii specifice problemei. Exemplu de secvent pentru
adunarea a dou momente de timp exprimate prin or, minut, secund:
s=s1+s2;
// secunde
if (s >=60) {
s=s-60; m1++;
}
m=m1+m2;
// minute
if (m >=60) {
m=m-60; h1++;
}
h=h1+h2;
// ore

Solutia fr instructiuni if este dat mai jos:


x=s1+s2; s= x%60;

// secunde

http://elth.srv.ro/

35
x=m1+m2 + x/60; m=x%/60;
// minute
h=h1+h2+x/60;
// ore

Instructiunea "switch"
Selectia multipl, dintre mai multe cazuri posibile, se poate face cu mai
multe instructiuni if incluse unele n altele sau cu instructiunea switch.
Instructiunea switch face o enumerare a cazurilor posibile (fiecare precedat de
cuvntul cheie "case") ntre acolade si foloseste o expresie de selectie, cu
rezultat ntreg. Forma general este:
switch(e) {
case c1: s1;
case c2: s2;
. . .
default: s;
}

//
//
//
//

// e= expresie de selectie
cazul c1
cazul c2
alte cazuri
cazul implicit ( poate lipsi)

unde: c1,c2,.. sunt constante sau expresii constante ntregi (inclusiv char)
s, s1, s2 ... sunt secvente de instructiuni (cu sau fr acolade)
Dac secventele de instructiuni nu se termin cu break, atunci secventa
echivalent cu instructiuni if este urmtoarea:
if (e==c1) { s1}
if (e==c2) {s2}
. . .
else {s}
// daca e difera de c1,c2,...

Deseori cazurile enumerate se exclud reciproc si fiecare secvent de


instructiuni se termin cu break, pentru ca dup selectia unui caz s se sar
dup blocul switch. Exemplu:
swich( c=getchar()) {
// c poate fi +,-,*,/
case '+': c=a+b; break;
case '-': c=a-b; break;
case '*': c=a*b; break;
case '/': c=a/b; break;
default:
error();
// tratare erori
}

Prin definitia instructiunii switch dup executarea instructiunilor unui caz se


trece la cazul imediat urmtor (n lipsa unei instructiuni break). Aceast
interpretare permite ca mai multe cazuri s foloseasc n comun aceleasi
operatii (partial sau n totalitate). Exemple:

http://elth.srv.ro/

36

// determinare semn numar din primul caracter citit


switch (c=getchar()) {
// c este semn sau cifra
case - : semn=1; c=getchar(); break;
case +: c=getchar();
// si semn=0
default: semn=0;
// semn implicit
}
// determina nr de zile dintr-o lun a unui
nebisect
switch (luna) {
case 2: zile=28; break; // februarie
// aprilie, iunie,..., noiembrie
case 4: case 6: case 9: case 11: zile =30; break;
// ianuarie, martie, mai,.. decembrie
default: zile=31; break;
// celelalte (1,3,5,..)
}

an

Cazul default poate lipsi, dar cnd este prezent atunci este selectat cnd
valoarea expresiei de selectie difer de toate cazurile enumerate explicit.

Macroinstructiunea assert
Macroinstructiunea assert, definit n fisierul <assert.h>, poate nlocui o
instructiune if si este folosit pentru verificarea unor conditii , fr a ncrca
programele cu instructiuni de verificare, care le-ar face mai greu de citit.
O asertiune este o afirmatie presupus a fi adevrat, dar care se poate
dovedi fals.
Utilizarea este similar cu apelul unei functii de tip void, cu un argument al
crei rezultat poate fi adevrat sau fals (nenul sau nul). Parametrul efectiv
este o expresie de relatie sau logic care exprim conditia verificat. Dac
rezultatul expresiei din assert este nenul (adevrat) atunci programul continu
normal, dar dac expresia este nul (fals) atunci se afiseaz un mesaj care
include expresia testat, numele fisierului surs si numrul liniei din fisier,
dup care programul se opreste. Exemple:
assert ( n <= MAX);
assert ( a > 0 && b > 0);

Prin simplitatea de utilizare assert ncurajeaz efectuarea ct mai multor


verificri asupra corectitudinii datelor initiale citite sau primite ca argumente
de functii si asupra unor rezultate intermediare.
Macroinstructiunea assert se foloseste mai ales n etapa de punere la punct a
programelor, deoarece pentru versiunea final se prefer afisarea unor mesaje

http://elth.srv.ro/

37
mai explicite pentru utilizatorii programului, eventual n alt limb dect
engleza, nsotite de semnale sonore sau de imagini (pentru programe cu
interfat grafic). De asemenea, assert se poate folosi pentru erori foarte
putin probabile dar posibile.
Erorile la operatii de citire de la consol sunt recuperabile, n sensul c se
poate cere operatorului repetarea introducerii, si nu se va folosi assert.
Eliminarea tuturor apelurilor assert dintr-un program se poate face printr-o
directiv de compilare plasat la nceputul programului.

4. Prelucrri repetitive n C
Instructiunea "while"
Instructiunea "while" exprim structura de ciclu cu conditie initial (si cu
numr necunoscut de pasi) si are forma urmtoare:
while (e) i
unde e este o expresie, iar i este o instructiune (instr. expresie, bloc, instr.
de control)
Efectul este acela de executare repetat a instructiunii continute n
instructiunea "while" ct timp expresia din paranteze are o valoare nenul
(este adevarat). Este posibil ca numrul de repetri s fie zero dac expresia
are valoarea zero de la nceput. Exemplu:
// cmmdc prin incercari succesive de posibili divizori
d= min(a,b);
// divizor maxim posibil
while (a%d || b%d)
d=d-1;
// incearca alt numar mai mic

In exemplul anterior, dac a=8 si b=4 atunci rezultatul este d=4 si nu se


execut niciodat instructiunea din ciclu (d=d-1).

http://elth.srv.ro/

38
Ca si n cazul altor instructiuni de control, este posibil s se repete un bloc
de instructiuni sau o alt instructiune de control. Exemplu:
// determinare cmmdc prin algoritmul lui Euclid
while (a%b > 0) {
r=a%b;
// restul impartirii a prin b
a=b; b=r;
}
// la iesirea din ciclu b este
cmmdc

Este posibil ca n expresia din instructiunea while s se efectueze atribuiri


sau apeluri de functii nainte de a compara rezultatul operatiei efectuate.
Exemplu:
// algoritmul lui Euclid
while (r=a%b) {
a=b; b=r;
}
// b este cmmdc

Instructiunea "for"
Instructiunea "for" din C permite exprimarea compact a ciclurilor cu
conditie initial sau a ciclurilor cu numr cunoscut de pasi si are forma:
for (exp1; exp2; exp3)

instructiune

Efectul acestei instructiuni este echivalent cu al secventei urmtoare:


exp1;
while (exp2){
instructiune
exp3;
}

// operatii de initializare
// cat timp exp2 !=0 repeta
// o instructiune expresie

Oricare din cele 3 expresii pot fi expresii vide, dar nu pot lipsi separatorii de
expresii (caracterul ';'). Dac lipseste "exp2" atunci se consider ca exp2 are
valoarea 1, deci ciclul se va repeta neconditionat. Exemplu de ciclu infinit (sau
din care se va iesi cu break sau return):
// repetare fara sfarsit
for (;;) instructiune
// sau while(1) instructiune

http://elth.srv.ro/

39
Cel mai frecvent instructiunea for se foloseste pentru programarea ciclurilor
cu numr cunoscut de pasi (cu contor). Exemple:
// stergere ecran prin defilare repetata de 24 ori
for (k=1;k<=24;k++)
putchar('\n');
// avans la linie noua
// alta secventa de stergere ecran de 25 de linii
for (k=24;k>0;k--)
putchar('\n');

Exemplul urmtor arat cum se poate folosi for n loc de while:


// determinare cmmdc pornind de la definitie
for (d=min(a,b); a%d || b%d; d--)
;
// repeta nimic
// determinare cmmdc pornind de la definitie
d=min(a,b);
// sau o instr. "if"
for (; a%d || b%d;)
d--;

Cele trei expresii din instructiunea for sunt separate prin ';' deoarece o
expresie poate contine operatorul virgul (','). Este posibil ca prima sau ultima
expresie s reuneasc mai multe expresii separate prin virgule. Exemplu:
// calcul factorial de n
for (nf=1,k=1 ; k<=n ; nf=nf*k,k++)
;
// repeta instr. vida

Este posibil mutarea unor instructiuni din ciclu n paranteza instructiunii


for, ca expresii, si invers - mutarea unor operatii repetate n afara parantezei.
Pentru calculul lui n! probabil se va scrie instructiunea urmtoare:
// calcul factorial de n
for (nf=k=1 ; k<=n ; k++)
nf = nf*k;

In general vom prefera programele mai usor de nteles (si de modificat) fat
de programele mai scurte dar mai criptice.

Instructiunea "do"

http://elth.srv.ro/

40
Instructiunea do-while se foloseste pentru exprimarea ciclurilor cu conditie
final, cicluri care se repet cel putin o dat. Forma uzual a instructiunii do
este urmtoarea:
do i while (e);
do { i } while (e);

Acoladele pot lipsi dac se repet o singur instructiune, dar chiar si atunci
se recomand folosirea lor. Exemplu de utilizare a instructiunii "do":
// calcul radical
succesive
r2=x;
//
do {
r1=r2;
//
r2=(r1+x/r1)/2;
//
} while ( abs(r2-r1));

din

prin

aproximatii

aproximatia initiala
r1 este aprox. veche
r2 este aprox. mai noua
// pana cand r2==r1

Un ciclu do tipic apare la citirea cu validare a unei valori, citire repetat


pn la introducerea corect a valorii respective. Exemplu:
do {
printf ("n=");
scanf("%d", &n);
} while (n>1000);

// n trebuie sa fie sub 1000

Putem folosi un ciclu do si pentru verificarea unor functii cu diferite date


initiale:
do {
printf("x="); scanf("%f",&x);

// citeste un

x
printf ("sqrt(%f)= %lf \n", x,sqrt(x));
} while (x>0);

Motivatia instructiunii do este aceea c expresia verificat contine valori


calculate (citite) n operatiile din ciclu, deci (aparent) expresia trebuie plasat
dup instructiunile din ciclu si nu naintea lor (ca n cazul instructiunii while).
Cu pretul repetrii unor instructiuni, un ciclu do poate fi rescris ca ciclu while
// echivalent cu:
i ;
while (e)
i ;

do i while(e);

Exemplu de citire repetat cu validare:

http://elth.srv.ro/

41

printf (n=); scanf (%d,&n);


// prima citire
while ( n > 1000) {
// daca n<=1000 se termin
printf ( Eroare, repetati introducerea lui n :);
scanf(%d,&n);
}

Instructiunile "break" si "continue"


Instructiunea break permite iesirea fortat dintr-un ciclu sau dintr-o structur
switch. Efectul instructiunii break este un salt imediat dup instructiunea
while, do, for sau switch. Exemple:
// determinare cmmdc pornind de la definitie
for (d=min(a,b); d>0; d--)
if (a%d==0 && b%d==0)
break;
printf ("%d \n",d);
// d este cmmdc(a,b)
// verifica daca un numar dat n este prim
for (k=2; k<n;k++)
if ( n%k==0)
break;
if (k==n) printf ("prim \n");
else printf ("neprim \n");

Un ciclu din care se poate iesi dup un numr cunoscut de pasi sau la
ndeplinirea unei conditii (iesire fortat) este de obicei urmat de o instructiune
if care stabileste cum s-a iesit din ciclu: fie dup numrul maxim de pasi, fie
mai nainte datorit satisfacerii conditiei.
Utilizarea instructiunii break poate simplifica expresiile din while sau for si
poate contribui la urmrirea mai usoar a programelor, desi putem evita
instructiunea break prin complicarea expresiei testate n for sau while.
Secventele urmtoare sunt echivalente:
for (k=0 ; k<n; k++)
if (e) break;
for (k=0 ; k<n && !e ; k++)
;

Exemple de cicluri cu iesire fortat care nu folosesc instructiunea break:


// verifica daca n este prim
for (k=2; k<n && n%k ; k++)
;

http://elth.srv.ro/

42
printf ( k==n? prim: neprim);
// verifica daca n este prim
for (prim=1,k=2; k<n && prim;k++)
if (n%k==0)
prim=0;
printf (prim? prim:neprim);

Instructiunea continue este si mai rar folosit fat de break si are ca efect un
salt la prima instructiune din ciclu, pentru reluarea sa. Exemplu :
// numararea comentariilor dintr-un text C
nc=0;
// nc= nr de comentarii
while ((c=getchar() != -1) {
// -1 daca s-a tastat ^Z
if (c !=/)
continue;
// salt peste instruct. urmatoare
c=getchar();
// caracterul imediat urmator
if (c==/ ' || c==*)
++nc;
// este inceput de comentariu
}

Instructiunea continue poate fi evitat prin inversarea conditiei care o


precede. Exemplu:
nc=0;
// nr de comentarii C
while ((c=getchar() !=-1)
if ( c ==/ && ((c=getchar()) ==/ || c==*) )
++nc;

Vectori n limbajul C
Prin "vector" se ntelege n programare o colectie liniar de date omogene
(toate de acelasi tip). In limba englez se foloseste si cuvntul "array" pentru
vectori si matrice. Fiecare element din vector este identificat printr-un indice
ntreg, pozitiv care arat pozitia sa n vector. La o prim vedere vectorii sunt
declarati si folositi n limbajul C n mod asemntor cu alte limbaje. Ulterior
vom arta c un nume de vector este similar cu un pointer si c este posibil o
tratare diferit a componentelor unui vector (fat de alte limbaje).
O alt particularitate a vectorilor n C este numerotarea elementelor de la
zero, deci primul element din orice vector are indicele zero, iar ultimul
element dintr-un vector are un indice mai mic cu 1 dect numrul elementelor
din vector. Exemplu:
// suma elementelor 0..n-1 dintr-un vector
for (i=0; i<n; i++)
s = s + a[i];

http://elth.srv.ro/

43
Anumite aplicatii (cu grafuri sau cu matrice de exemplu) folosesc n mod
traditional o numerotare de la 1 ( nu exist un nod zero ntr-un graf). O solutie
simpl este nefolosirea primei pozitii din vector (pozitia zero) si o alocare
suplimentar de memorie, pentru a folosi pozitiile 1..n dintr-un vector cu n+1
elemente. Exemplu:
// suma elementelor 1..n dintr-un vector
for (i=1; i<=n; i++)
s = s + a[i];

Utilizarea unui vector presupune repetarea unor operatii asupra fiecarui


element din vector deci folosirea unor structuri repetitive.
Exemplu de program care citeste si afiseaz un vector de ntregi:
void main () {
int a[100],n,i;
// vectorul a de max 100 de intregi
scanf ("%d",&n); // citeste nr de elemente vector
for (i=0;i<n;i++)
scanf ("%d", &a[i]); // citire elemente vector
for (i=0;i<n;i++)
printf ("%d ", a[i]); // scrie elemente vector
}

In exemplul anterior memoria pentru vector este alocat la compilare si nu


mai poate fi extins la executie. Programatorul trebuie s estimeze o
dimensiune maxim pentru vector, care este o limit a programului. De obicei
se folosesc constante simbolice pentru aceste dimensiuni si se verific
ncadrarea datelor citite n dimensiunile maxime. Exemplu:
#define MAX 100
// dimensiune maxima vectori
void main () {
int a[MAX], n,i;
scanf ("%d", &n);
// citeste dimensiune efectiva
if ( n > MAX) {
printf ("Eroare: n > %d \n",MAX); return;
}
...
// citire si utilizare elemente vector

La declararea unui vector se poate face initializarea partial sau integral a


componentelor, folosind o list de constante ntre acolade. Exemple:
int
int
int
int

azi[3]={01,04,2001}; // zi,luna,an
xmas[]={25,12,2000}; // dimensiune=3
prime[1000]={1,2,3}; // restul elememtelor zero
a[1000]={0};
// toate elementele initial zero

http://elth.srv.ro/

44
De observat c notatiile cu indici din matematic nu se traduc automat n C
pentru c uneori elementele unui sir de numere nu sunt necesare simultan n
memorie si se folosesc succesiv, putnd fi memorate pe rnd ntr-o singur
variabil. Exemplu:
// calcul exp(x) prin dezvoltare in serie de puteri
s=t=1;
// s=t[0]=1;
for (k=1;k<=n;k++){
t=t*x/k; s=s+t;
// t[k] *= x/k; s += t[k];
}

Matrice n limbajul C
O matrice bidimensional este privit n C ca un vector cu componente
vectori, deci un vector de linii. Exemplu de matrice cu dimensiuni constante:
int a[20][10];

// maxim 20 linii si 10 coloane

Notatia a[i][j] desemneaz elementul din linia i si coloana j a unei


matrice a, sau elementul j din vectorul a[i].
Este posibil initializarea unei matrice la definirea ei, iar elementele care nu
sunt initializate explicit primesc valoarea zero. Exemple:
float unu[3][3] = { {1,0,0}, {0,1,0}, {0,0,1} );
int a[10][10] ={0};
// toate elementele zero

Prelucrarea elementelor unei matrice se face prin dou cicluri; un ciclu


repetat pentru fiecare linie si un ciclu pentru fiecare coloan dintr-o linie:
// afisare matrice cu nl linii si nc coloane
for (i=0;i<nl;i++) {
for (j=0;j<nc;j++)
printf (%6d, a[i][j]);
printf(\n);
}

Numrul de cicluri incluse poate fi mai mare dac la fiecare element de


matrice se fac prelucrri repetate. De exemplu, la nmultirea a dou matrice,
fiecare element al matricei rezultat se obtine ca o sum:
for (i=0;i<n;i++)
for (j=0;j<m;j++) {
c[i][j]=0;
for (k=0;k<p;k++)
c[i][j] += a[i][k]*b[k][j];

http://elth.srv.ro/

45
}

In C matricele sunt liniarizate pe linii, deci n memorie linia 0 este urmat de


linia 1, linia 1 este urmat de linia 2 s.a.m.d.
Numerotarea liniilor si coloanelor din C este diferit de numerotarea uzual
din matematic (care ncepe de la 1 si nu de la 0), folosit pentru datele initiale
si rezultatele programelor numerice. O solutie este nefolosirea liniei 0 si
coloanei 0, iar alt solutie este modificarea indicilor cu 1.
In exemplul urmtor se citesc arce dintr-un graf orientat (cu nodurile 1..n) si
se creeaz o matrice de adiacente n care a[i][j]=1 dac exist arc de la nodul
i la nodul j si a[i][j]=0 dac nu exist arcul i-j.
char a[20][20]={0}; int i,j,n;
printf(numar noduri: ); scanf (%d,&n);
printf (" lista arce: \n");
while ( scanf ("%d%d",&i,&j) != EOF)
a[i][j]=1;
printf ( matrice de adiacente: \n);
for (i=1;i<=n;i++) {
for (j=1;j<=n;j++)
printf("%2d",a[i][j]);
printf("\n");
}

Functia scanf are un rezultat ntreg egal cu numrul de valori (cmpuri)


citite sau -1 (constanta simbolic EOF) dac s-a citit caracterul terminator de
date (end of file). Acest caracter este Ctrl+Z n MS-DOS si MS-Windows.
Programare structurat n limbajul C
Desi exist o instructiune goto n limbajul C se pot scrie orice programe fr
a recurge la aceast instructiune.
O situatie care ar putea justifica folosirea instructiunii goto ar fi iesirea dintrun ciclu interior direct n afara ciclului exterior. Exemplu:
// cauta prima aparitie a lui b in matricea a
for (i=0;i<n;i++)
for (j=0;j<n;j++)
if ( a[i][j]==b )
goto gasit;
printf ("negasit \n");
return;
gasit:
printf("gasit in linia %d si coloana %d \n",i,j);

http://elth.srv.ro/

46
Existenta instructiunilor break, continue si return este considerat mpotriva
normelor programrii structurate, pentru c sunt salturi mascate (cu adres
implicit). Totusi instructiunile break si return sunt mult folosite pentru
iesirea fortat din cicluri deoarece reduc lungimea codului surs.
Un ciclu for care contine o instructiune if este n general diferit de un ciclu
while, desi uneori se pot folosi ca solutii alternative. Exemple de secvente
echivalente ca efect:
// verifica daca n este prim cu n-2 impartiri (orice
n)
prim=1;
for (k=2;k<n;k++)
if (n%k==0)
prim=0;
// varianta cu mai putine
divizor)
k=2; prim=1;
while ( n%k && k<n)
k++;
if (k==n) prim=0;

incercari

(pn

la

un

// varianta cu for si iesire fortata din ciclu


for (prim=1,k=2; k<n && prim; k++)
if (n%k==0)
prim=0;

In general logica de rezolvare a unei probleme impune structurile de control


folosite, dar uneori avem de ales ntre dou sau mai multe alternative de
codificare a unui algoritm. In problema urmtoare se d un vector de coduri
ale unor produse si un vector de cantitti ale acestor produse si se cere
totalizarea cantittilor pentru fiecare produs n parte. Exemplu de date initiale
(vector de coduri neordonat, cu repetarea unor coduri):
Cod : 2
Cant: 10

7
10

2
10

3
10

7
10

2
10

3
10

7
10

2
10

Rezultate pentru aceste date:


Cod: 2
Cant: 40

7
30

3
20

Dac vectorul de coduri este ordonat se pot utiliza dou cicluri while : un
ciclu (interior) repetat pentru produsele cu acelasi cod, inclus ntr-un ciclu
(exterior) repetat ct timp mai exist elemente n vectori. In locul celor dou

http://elth.srv.ro/

47
cicluri se poate folosi un singur ciclu for, repetat pentru toate elementele
vectorului, care contine un if pentru a verifica trecerea de la un produs la altul
(schimbarea codului la naintarea n vectorul de coduri).
// totalizare cu doua cicluri while
i=0;
while (i < n) {
c=cod[i]; sum=0;
while ( c == cod[i] )
sum=sum+val[i++];
printf ("%6d %6d \n", c,sum);
}
// totalizare cu un singur ciclu
c=cod[0]; sum=val[0];
for (i=1;i<n;i++) {
if ( c == cod[i])
sum=sum+val[i];
else {
printf ("%6d %6d \n",c,sum);
c=cod[i]; sum=val[i];
}
}
printf ("%6d %6d \n",c,sum);
// ultima grupa

Dac vectorii sunt neordonati atunci fiecare cod de produs este cutat n
elementele care i urmeaz n vectorul de coduri si se nsumeaz cantitatile
respective. In acest caz problema este de a marca produsele deja luate n
considerare, pentru a nu aduna de mai multe ori aceeasi cantitate. Se poate
folosi un vector auxiliar, cu cantittile totale pe coduri si o cutare repetat n
acest vector, pentru a afla produsele deja prelucrate.
5. Programare modular n C
Importanta functiilor n programare
Practic nu exist program care s nu apeleze functii din bibliotecile existente
si care s nu contin definitii de functii specifice aplicatiei respective.
Motivele utilizrii de subprograme sunt multiple:
- Un program mare poate fi mai usor de scris, de nteles si de modificat dac
este modular, deci format din module functionale relativ mici.
- Un subprogram poate fi reutilizat n mai multe aplicatii, ceea ce reduce
efortul de programare al unei noi aplicatii.

http://elth.srv.ro/

48
- Un subprogram poate fi scris si verificat separat de restul aplicatiei, ceea ce
reduce timpul de punere la punct a unei aplicatii mari (deoarece erorile pot
apare numai la comunicarea ntre subprograme corecte).
- Intretinerea unei aplicatii este simplificat, deoarece modificrile se fac
numai n anumite subprograme si nu afecteaz alte subprograme (care nici nu
mai trebuie recompilate).
Utilizarea de functii permite dezvoltarea progresiv a unui program mare, fie
de jos n sus (bottom up), fie de sus n jos (top down), fie combinat.
In limbajele anterioare limbajului C subprogramele erau de dou feluri:
- Functii, care au un singur rezultat, asociat cu numele functiei.
- Proceduri (subrutine), care pot avea mai multe rezultate sau nici unul, iar
numele nu are asociat nici o valoare.
In limbajul C exist numai functii, dar pentru functiile fr rezultat direct
(asociat numelui functiei) s-a introdus tipul void. Pentru o functie cu rezultat
direct tipul functiei este tipul rezultatului.

Utilizarea functiilor n C
O functie de tip void se va apela printr-o instructiune expresie. Exemple:
printf (\n n=);
clearerr (stdin);

// sterge indicator de eroare si

EOF

O functie de un tip diferit de void este apelat prin folosirea ei ca operand


ntr-o expresie. Exemple:
z=sqrt(x)+ sqrt(y);
printf ("%lf \n", sqrt(x));
comb = fact(n)/( fact(k)*fact(n-k));
// combinari
y = atan (tan(x));
//functie in functie

In limbajul C este uzual ca o functie s raporteze prin rezultatul ei (numr


ntreg) modul de terminare (normal/cu eroare) sau numrul de valori
citite/scrise (la functiile de intrare-iesire). Uneori acest rezultat este ignorat iar
functia cu rezultat este apelat ca o functie void. Exemple:
scanf ("%d",&n); // rezultatul lui scanf este 1
getch();
// rezultatul este caracterul citit
gets(adr);
// rezultatul este adresa "adr"

Cnd se declar prototipul unei functii cu argumente este suficient s se


declare tipul argumentelor, iar numele argumentelor formale pot lipsi.
Exemplu:

http://elth.srv.ro/

49

double unghi(double,double,double);//3 argumente double

Argumentele folosite la apelul functiei se numesc argumente efective si pot


fi orice expresii (constante, functii etc.). Argumentele efective trebuie s
corespund ca numr si ca ordine (ca semnificatie) cu argumentele formale (cu
exceptia unor functii cu numr variabil de argumente). Exemplu de functie
unde ordinea argumentelor este important:
// calculul unui unghi dintr-un triunghi
double unghi (double a, double b, double c) {
return acos ((b*b+c*c-a*a)/(2.*b*c)); // unghiul A
}
// utilizari
ua = unghi(a,b,c); ub=unghi(b,c,a); uc = unghi(c,c,b);

Este posibil ca tipul unui argument efectiv s difere de tipul argumentului


formal corespunztor, cu conditia ca tipurile s fie "compatibile" la atribuire.
Conversia de tip (ntre numere sau pointeri) se face automat, la fel ca si la
atribuire. Exemple:
x=sqrt(2);
"int"
y=pow(2,3);

//

arg.

formal

"double",

arg.efectiv

//

arg. formale de tip "double"

Deci o functie cu argument formal de un tip numeric (de ex. int) poate fi
apelat cu argumente efective de orice tip numeric (inclusiv long, float,
double, long double).
De retinut c nu toate erorile de utilizare a functiilor pot fi semnalate de
compilator si se pot manifesta la executie prin rezultate gresite. Exemplu:
printf(%d,pow(10,3));

// (int) pow(10,3)

Definirea de functii n C
Sintaxa definirii functiilor n C s-a modificat de la prima versiune a
limbajului la versiunea actual (standardizat), pentru a permite verificarea
utilizrii corecte a oricrei functii la compilare. Forma general a unei definitii
de functie, conform standardului, este:
tipf numef (tip1 arg1, tip2 arg2, ...) {
declaratii
instructiuni (blocuri)
}

http://elth.srv.ro/

50

unde:
tipf este tipul functiei (tipul rezultatului sau void)
tip1, tip2,... sunt tipurile argumentelor (parametrilor) functiei
Tipul unei functii C poate fi orice tip numeric, orice tip pointer, orice tip
structur (struct) sau void. Exemplu de functie de tip void:
// sterge ecran prin defilare cu 24 de linii
void erase () {
int i;
for (i=0;i<24;i++)
printf("\n");
}

Este preferabil ca definitia functiei erase s precead definitia functiei


main (sau a unei alte functii care o apeleaz). Dac functia erase este
definit dup functia main atunci este necesar o declaratie pentru functia
erase naintea functiei main:
void erase ();
void main () {
erase(); . . .
}
void erase() {
. . .
}

// declaratie functie
// utilizare functie
// definitie functie

In lipsa unei declaratii de tip explicite se consider c tipul implicit al


functiei este int. Functia main poate fi declarat fie de tip void, fie de tip int,
explicit sau implicit.
Variabilele definite ntr-o functie pot fi folosite numai n functia respectiv,
cu exceptia celor declarate extern. Pot exista variabile cu aceleasi nume n
functii diferite, dar ele se refer la adrese de memorie diferite.
O functie are n general un numr de argumente formale (fictive), prin care
primeste datele initiale necesare si poate transmite rezultatele functiei. Aceste
argumente pot fi doar nume de variabile (nu orice expresii) cu tipul declarat n
lista de argumente, pentru fiecare argument n parte. Exemplu:
int comb (int n, int k) {
k
int i, cmb=1;
for (i=1;i<=k;i++)
cmb = cmb * (n-i+1)/i;
return cmb;
}

// combinari de n luate cate

http://elth.srv.ro/

51
In limbajul C se pot defini si functii cu numr variabil de argumente, care
pot fi apelate cu numr diferit de argumente efective. Exemplu de functie
pentru adunarea unui numr oarecare de valori:
#include <stdarg.h>
int va_add(int numberOfArgs, ...) {
va_list ap;
// tip definit in <stdarg.h>
int n = numberOfArgs;
// numar de argumente efective
int sum = 0;
va_start(ap,numberOfArgs); // macro din <stdarg.h>
while (n--)
sum += va_arg(ap,int);
va_end(ap);
// macro din <stdarg.h>
return sum;
}
// exemple de apelare
va_add(3,987,876,567);
// cu 3 arg
va_add(2,456,789);
// cu 2 arg

Instructiunea return
Instructiunea return se foloseste pentru revenirea dintr-o functie apelat la
functia care a fcut apelul si poate contine o expresie ce reprezint rezultatul
functiei. Conversia rezultatului la tipul functiei se face automat, dac e posibil
O functie de un tip diferit de void trebuie s contin cel putin o instructiune
return prin care se transmite rezultatul functiei. Exemplu:
long fact (int n) {
long nf=1L;
while ( n)
nf=nf*n--;
return nf;
}

// factorial de n
// ptr calcul rezultat
// nf=nf*n; n=n-1;
// rezultat functie

O functie poate contine mai multe instructiuni return. Exemplu:


char toupper(char c) {
if (c>='a'&& c<='z')
return c+'A'-'a';
else
return c;
}

// trece car. c in litere mari


// daca c este litera mica
// cod litera mare
// altceva decat litera mica
// ramane neschimbat

Cuvntul else dup o instructiune return poate lipsi, dar de multe ori este
prezent pentru a face codul mai clar. Exemplu fr else:

http://elth.srv.ro/

52
char toupper(char c) {
if (c>='a'&& c<='z')
return c+'A'-'a';
return c;
}

// trece car. c in litere mari


// daca c este litera mica
// cod litera mare
// ramane neschimbat

Instructiunea return poate fi folosit pentru iesirea fortat dintr-un ciclu si


din functie, cu reducerea lungimii codului surs. Exemplu:
// verifica daca un numar dat este prim
int esteprim (int n) {
int k;
for (k=2;k<=n/2;k++)
if (n%k==0)
return 0;
// nu este prim
return 1;
// este prim
}

Intr-o functie de tip void se poate folosi intructiunea return fr nici o


expresie, iar dac lipseste se adaug automat ca ultim instructiune. In main
instructiunea return are ca efect terminarea ntregului program.
O functie C nu poate avea ca rezultat direct un vector, dar poate modifica
elementele unui vector primit ca argument. Exemplu:
// genereaza vector cu cifrele unui nr.natural dat n
void cifre (int n, char c[5] ) {
int k;
for (k=4;k>=0;k--) {
c[k]=n%10;
// cifra din pozitia k
n=n/10;
}
}

In exemplul anterior vectorul are dimensiune fix (5) si contine toate


zerourile initiale, dar putem defini o functie de tip int cu rezultat egal cu
numrul cifrelor semnificative. Pentru argumentele formale de tip vector nu
trebuie specificat dimensiunea vectorului. Exemplu:
int cifre (int n, char c[] ) { . . . }

Transmiterea de date ntre functii


Transmiterea argumentelor efective la apelul unei functii se face n C prin
copierea valorilor argumentelor efective n argumentele formale (care sunt
variabile locale ale functiei). In acest fel functia apelat lucreaz cu duplicate

http://elth.srv.ro/

53
ale variabilelor argumente efective si nu poate modifica accidental variabile
din functia apelant.
Compilatorul C genereaz o secvent de atribuiri la argumentele formale
nainte de efectuarea saltului la prima instructiune din functia apelat. Din
acest motiv toate conversiile de tip efectuate automat la atribuire se aplic si la
transmiterea argumentelor.
In functia "fact" se modifica aparent valoarea lui "n" dar de fapt se modific
o variabil local, fr s fie afectat variabila ce contine pe "n" n "main".
Un alt exemplu clasic este o functie care ncearc s schimbe ntre ele
valorile a dou variabile, primite ca argumente:
void swap (int a, int b) {

//

nu

este

corect

!!!
int aux;
aux=a; a=b; b=aux;
}
void main () {
int x=3, y=7;
swap(x,y);
printf ("%d,%d \n",x,y); // scrie 3,7 !
}

In general o functie C nu poate transmite rezultate si nu poate modifica


argumente de un tip numeric. In C pentru transmiterea de rezultate prin
argumente de ctre o functie trebuie s folosim argumente formale de tip
pointer (adrese de memorie).
Versiunea corect pentru functia swap este urmtoarea:
void swap (int * pa, int * pb) {
intregi
int aux;
aux=*pa; *pa=*pb; *pb=aux;
}

// pointeri la

Apelul acestei functii foloseste argumente efective pointeri:


int x,y; . . .
swap (&x,&y);

// schimba valorile x si y intre

ele

Functia scanf este un exemplu de functie care transmite rezultate (valorile


citite) prin intermediul parametrilor si este apelat cu argumente de tip pointer.
Folosirea de variabile pointer ca argumente formale pentru transmiterea de
rezultate va fi discutat ulterior.
Pentru variabilele locale memoria se aloc la activarea functiei (deci la
executie) si este eliberat la terminarea executrii functiei. Initializarea

http://elth.srv.ro/

54
variabilelor locale se face tot la executie si de aceea se pot folosi expresii
pentru initializare (nu numai constante). Exemplu:
double arie (double a, double b, double c) {
double p = (a+b+c)/2.;
// initializare cu expresie
return sqrt(p*(p-a)*(p-b)*(p-c));
}

Practic nu exist nici o diferent ntre initializarea unei variabile locale la


declarare sau printr-o instructiune de atribuire.
Probleme pot apare la argumentele de functii de tip matrice din cauza
interpretrii diferite a zonei ce contine elementele matricei de ctre functia
apelat si respectiv de functia apelant. Pentru a interpreta la fel matricea
liniarizat este important ca cele dou functii s foloseasc acelasi numr de
coloane n formula de liniarizare. Din acest motiv nu este permis absenta
numrului de coloane din declaratia unui argument formal matrice. Exemplu
incorect sintactic:
void printmat ( int a[][], int nl, int nc);

// gresit

O solutie simpl dar care nu e posibil ntotdeauna ar fi specificarea


aceleeasi constante pentru numr de coloane n toate functiile si n definitia
matricei din programul principal. Exemplu:
void printmat(int a[][10], int nl, int nc); // nc <= 10

Unele compilatoare consider tipul argumentului a ca fiind pointer la un


vector de 10 ntregi si nu ca pointer la pointer la ntreg, pentru a forta
transmiterea numrului de coloane si a evita erori de transmitere a matricelor
la functii. Pentru functiile de bibliotec nu se poate preciza numrul de
coloane si trebuie gsite alte solutii de definire a acestor functii si/sau de
alocare a matricelor.
Functiile pot comunica date ntre ele si prin variabile externe, definite
naintea functiilor care le folosesc. Exemplu:
int a[20][20],n;
// variabile externe
void citmat() {
// citire matrice
int i,j;
printf ("n="); scanf("%d",&n); // dimensiuni
for (i=0;i<n;i++)
// citire matrice
for (j=0;j<n;j++)
scanf("%d",&a[i][j]);
}
void scrmat() {
// afisare matrice
int i,j;
for (i=0;i<n;i++) {

http://elth.srv.ro/

55
for (j=0;j<n;j++)
printf("%5d",a[i]);
printf(\n);
// dupa fiecare linie
}
}

Nu se recomand utilizarea de variabile externe dect n cazuri rare, cnd


mai multe functii folosesc n comun mai multe variabile si se doreste
simplificarea utilizrii functiilor, sau n cadrul unor biblioteci de functii.

Functii recursive
O functie recursiv este o functie care se apeleaz pe ea nssi. Se pot
deosebi dou feluri de functii recursive:
- Functii cu un singur apel recursiv, ca ultim instructiune, care se pot rescrie
usor sub forma nerecursiv (iterativ).
- Functii cu unul sau mai multe apeluri recursive, a cror form iterativ
trebuie s foloseasc o stiv pentru memorarea unor rezultate intermediare.
Recursivitatea este posibil n C datorit faptului c, la fiecare apel al
functiei, adresa de revenire, variabilele locale si parametri formali sunt
memorate ntr-o stiv (gestionat de compilator), iar la iesirea din functie (prin
return) se scot din stiv toate datele puse la intrarea n functie (se "descarc"
stiva).
Exemplu de functie recursiv de tip void :
void binar (int n) {
// se afiseaza n in
binar
if (n>0) {
binar(n/2);
// scrie echiv. binar al lui n/2
printf("%d",n%2); // si restul impartirii n la 2
}
}

Functia de mai sus nu scrie nimic pentru n=0, dar poate fi usor completat
cu o ramur else la instructiunea if.
Orice functie recursiv trebuie s contin (cel putin) o instructiune if (de
obicei chiar la nceput), prin care se verific dac (mai) este necesar un apel
recursiv sau se iese din functie. Reamintim c orice functie void primeste o
instructiune return ca ultim instructiune. Absenta instructiunii if conduce la o
recursivitate infinit ( la un ciclu fr conditie de terminare).
Pentru functiile de tip diferit de void apelul recursiv se face printr-o
instructiune return, prin care fiecare apel preia rezultatul apelului anterior.
Anumite functii recursive corespund unor relatii de recurent. Exemplu:

http://elth.srv.ro/

56

long fact (int n) {


if (n==0)
return 1L;
else
return n*fact(n-1);
}

// 0! = 1
// n!=n*(n-1)!

In exemplul urmtor se foloseste tot o relatie de recurent pentru algoritmul


lui Euclid:
int cmmdc (int a, int b) {
if ( b>0)
return cmmdc(b,a%b); // cmmdc(a,b)=cmmdc(b,a%b)
else
return a;
}

Pentru determinarea cmmdc mai exist si o alt functie recursiv.


Functiile recursive nu contin n general cicluri explicite (cu unele exceptii),
iar repetarea operatiilor este obtinut prin apelul recursiv.
O functie care contine un singur apel recursiv ca ultim instructiune poate fi
transformat ntr-o functie nerecursiv, nlocuind instructiunea if cu while.
int fact (int n) {
// recursiv
if (n>0)
return n*fact(n-1);
// n!=n*(n-1)!
else
return 1;
// 0! = 1
}
int fact (int n) {
int nf=1;
while (n>0){
nf= nf*n; n=n-1;
}
return nf;
}

// nerecursiv

Functiile recursive cu mai multe apeluri sau cu un apel care nu este ultima
instructiune pot fi rescrise iterativ numai prin folosirea unei stive. Aceast
stiv poate fi un simplu vector local functiei. Exemplu:
void binar ( int n) {
// afisare in binar
int c[16],i;
// c este stiva de cifre
// pune resturi in stiva c
i=0;
while ( n>0) {
c[i++]=n%2;

http://elth.srv.ro/

57
n=n/2;
}
// descarca stiva: scrie vector in ordine inversa
while (i>0)
printf ("%d",c[--i]);
}

Biblioteci de functii
Standardul limbajului C contine si o serie de functii care trebuie s existe n
toate implementrile limbajului. Declaratiile acestor functii sunt grupate n
fisiere antet cu acelasi nume pentru toate implementrile.
In afara acestor functii standard exist si alte functii specifice sistemului de
operare, precum si functii utile pentru anumite aplicatii (grafic pe calculator,
baze de date, aplicatii de retea s.a.).
Uneori, aceleasi operatii se pot realiza cu functii universale sau cu functii
dependente de sistem: obtinere/modificare timp, operatii cu directoare s.a.
Utilizarea functiilor standard din biblioteci reduce timpul de dezvoltare a
programelor, mreste portabilitatea lor si contribuie la reducerea diversittii
programelor, cu efect asupra usurintei de citire si de ntelegere a lor.
Functiile de bibliotec nestandard utilizate ar trebui marcate prin comentarii.
Informatii complete asupra functiilor de bibliotec pot fi obtinute prin ajutor
(Help) oferit de orice mediu IDE sau prin examinarea fisierelor antet, de tip H.
Aici se vor enumera succint cteva grupuri de functii standard utile:
<stdio.h> Functii standard de intrare-iesire pentru consol si fisiere
<stdlib.h> Functii de alocare memorie, de conversie din caractere n binar
(atoi, atol, atof), de sortare si cutare (qsort, bsearch), functii diverse (exit).
<math.h> Functii standard matematice (cu rezultat si argumente double)
(abs,sqrt,pow,sin,cos,exp,log s.a.)
<string.h> Functii standard pentru operatii cu siruri de caractere
<ctype.h> Functii de verificare tip caractere si de conversie caractere
<time.h> Functii pentru operatii cu timpi si date calendaristice
<stdarg.h> Functii (macrouri) pentru functii cu numr variabil de argumente
<io.h>
Functii standard de intrare-iesire stil Unix
<conio.h> Functii de intrare-iesire cu consola (ecranul si tastatura)
<process.h> Functii pentru executie procese (taskuri)
6. Tipuri pointer n C
Variabile pointer

http://elth.srv.ro/

58
O variabil pointer poate avea ca valori adrese de memorie. Aceste adrese
pot fi:
- Adresa unei valori de un anumit tip (pointer la date)
- Adresa unei functii (pointer la o functie)
- Adresa unei zone cu continut necunoscut (pointer la void).
Cel mai frecvent se folosesc pointeri la date.
Exist o singur constant de tip pointer, cu numele NULL (valoare zero) si
care este compatibil la atribuire si comparare cu orice tip pointer. Totusi, se
poate atribui o constant ntreag convertit la un tip pointer unei variabile
pointer. Exemplu:
char * p = (char*)10000;

// o adresa de memorie

Desi adresele de memorie sunt de multe ori numere ntregi pozitive, tipurile
pointer sunt diferite de tipurile ntregi si au utilizri diferite.
In limbajul C tipurile pointer se folosesc n principal pentru:
- Declararea si utilizarea de vectori, mai ales pentru vectori ce contin siruri de
caractere.
- Argumente de functii prin care se transmit rezultate (adresele unor variabile
din afara functiei).
- Acces la date alocate dinamic si care nu pot fi adresate printr-un nume.
- Argumente de functii prin care se transmite adresa unei alte functii.
Declararea unei variabile (sau argument formal) de un tip pointer include
declararea tipului datelor (sau functiei) la care se refer acel pointer. Sintaxa
declarrii unui pointer la o valoare de tipul tip este
tip * ptr;

Exemple de variabile si argumente pointer:


char * pc; // pc= adresa unui caracter sau sir de car.
int * pi; // pi= adresa unui intreg sau vector de int
void * p;
// p= adresa de memorie
int * * pp; // pp= adresa unui pointer la un intreg
int strlen (char* str); // str=adr. unui sir de
caractere

Atunci cnd se declar mai multe variabile pointer de acelasi tip, nu trebuie
omis asteriscul care arat ca este un pointer. Exemple:
int *p, m;
int *a, *b ;

// m de tip "int", p de tip "int*"


// a si b de tip pointer

Dac se declar un tip pointer cu typedef atunci se poate scrie astfel:

http://elth.srv.ro/

59

typedef int* intptr; // intptr este nume de tip


intptr p1,p2,p3;
// p1,p2,p3 sunt pointeri

Tipul unei variabile pointer este important pentru c determin cti octeti
vor fi folositi de la adresa continut n variabila pointer si cum vor fi
interpretati. Un pointer la void nu poate fi utilizat direct, deoarece nu se stie
cti octeti trebuie folositi si cum.

Operatii cu pointeri la date


Operatiile posibile cu variabile pointer pot fi rezumate astfel:
- Indirectarea printr-un pointer (diferit de void *), pentru acces la datele
adresate de acel pointer: operatorul unar '*'. Exemple:
*p = y;
x = *p;
*s1++ = *s2++;

- Atribuire la un pointer. In partea dreapt poate fi un pointer de acelasi tip


(eventual cu conversie de tip) sau constanta NULL sau o expresie cu rezultat
pointer. Exemple:
p1=p1;
p=NULL;
p=&x;
p=*pp; p =(int*)malloc(n);

Operatorul unar '&' aplicat unei variabile are ca rezultat adresa variabilei
respective (deci un pointer). Functia "malloc" si alte functii au ca rezultat un
pointer de tip void*.
Unei variabile de tip void* i se poate atribui orice alt tip de pointer fr
conversie de tip explicit si un argument formal de tip void* poate fi nlocuit
cu un argument efectiv de orice tip pointer.
Atribuirea ntre alte tipuri pointer se poate face numai cu conversie de tip
explicit ("cast") si permite interpretarea diferit a unor date din memorie. De
exemplu, putem extrage cei doi octeti dintr-un ntreg scurt astfel:
short int n; char c1, c2;
c1= *(char*)&n;
c2= *(char*)(&n+1);

sau:
char * p = (char*) &n;

http://elth.srv.ro/

60
c1= *p;

c2 = *(p+1);

- Compararea sau scderea a dou variabile pointer de acelasi tip (de obicei
adrese de elemente dintr-un acelasi vector). Exemplu:
// pozitia (indicele) n sirul s1 a sirului s2
// sau un numar negativ daca s1 nu contine pe s2
int pos ( char* s1, char * s2) {
char * p1 =strstr(s1,s2); // adresa lui s2 in s1
return p1-s1-1;
}

- Adunarea sau scderea unui ntreg la (din) un pointer, incrementarea si


decrementarea unui pointer. Exemplu:
// afisarea unui vector
void printVector ( int a[], int n) {
while (n--)
printf (%d , *a++);
}

Trebuie observat c incrementarea unui pointer si adunarea unui ntreg la un


pointer nu adun ntotdeauna ntregul 1 la adresa continut n pointer; valoarea
adaugat (sczut) depinde de tipul variabilei pointer si este egal cu produsul
dintre constant si numrul de octeti ocupat de tipul adresat de pointer. Pentru
un pointer p la tipul tip expresiile:
p = p+ c; ++p; // tip * p ;

sunt echivalente cu expresiile


p = p+c*sizeof(tip); p=p+sizeof(tip);

// tip * p ;

Aceast conventie permite referirea simpl la elemente succesive dintr-un


vector folosind indirectarea printr-o variabil pointer.
Operatorul unar sizeof se poate aplica unui nume de tip sau unei variabile si
are ca rezultat numrul de octeti alocati pentru tipul sau pentru variabila
respectiv:
char c; float f;
sizeof(char)= sizeof c = 1
sizeof(float) = sizeof f = 4

Operatorul sizeof permite scrierea unor programe portabile, care nu depind


de lungimea pe care se reprezint n memorie fiecare tip de date. De exemplu,
tipul int ocup uneori 2 octeti iar alteori 4 octeti.

http://elth.srv.ro/

61
O eroare frecvent este utilizarea unei variabile pointer care nu a primit o
valoare (adic o adres de memorie) prin atribuire sau prin initializare la
declarare. Efectul este accesul la o adres de memorie imprevizibil, chiar n
afara spatiului de memorie ocupat de programul ce contine eroarea. Exemple:
int * a;
// declarata dar neinitializata
while (scanf ("%d",a) > 0)
a++;

Vectori si pointeri
O variabil vector contine adresa de nceput a vectorului (adresa primei
componente din vector) si de aceea este echivalent cu un pointer la tipul
elementelor din vector. Aceasta echivalent este exploatat de obicei n
argumentele de tip vector si n lucrul cu vectori alocati dinamic.
O functie poate avea ca rezultat un pointer dar nu si rezultat vector.
Pentru declararea unei functii care primeste un vector de ntregi si
dimensiunea lui avem cel putin dou posibilitti:
void printVec (int a[], int n);
void printVec (int * a, int n);

In interiorul functiei ne putem referi la elementele vectorului "a" fie prin


indici, fie prin indirectare, indiferent de felul cum a fost declarat parametrul
vector "a". Exemplu:
// prin indexare
void printVec (int a[], int n) {
int i;
for (i=0;i<n;i++)
printf (%6d",a[i]);
}
// prin indirectare
void printVec (int *a, int n) {
int i;
for (i=0;i<n;i++)
printf (%6d", *a++);
}

Citirea elementelor unui vector se poate face asemntor:


for (i=0;i<n;i++)
scanf ("%d", a+i);
a++

// echivalent cu &a[i] si cu

http://elth.srv.ro/

62

In general, exist urmtoarele echivalente de notatie pentru un vector "a":


a[0] *a
a[1] *(a+1)
a[k] *(a+k)

&a[0]
&a[1]
&a[k]

a
a+1
a+k

Aritmetica cu pointeri este diferit de aritmetica cu numere ntregi.


In aplicatiile numerice se prefer argumentele de tip vector si adresarea cu
indici, iar n functiile cu siruri de caractere se prefer argumente de tip pointer
si adresarea indirect prin pointeri.
Diferenta major dintre o variabil pointer si un nume de vector este aceea
c un nume de vector este un pointer constant (adresa este alocat de
compilatorul C si nu mai poate fi modificat la executie) Un nume de vector
nu poate apare n stnga unei atribuiri, n timp ce o variabil pointer are un
continut modificabil prin atribuire sau prin operatii aritmetice. Exemple:
int a[100], *p;
p=a; ++p; // corect
a=p; ++a; // incorect

Declararea unui vector (alocat la compilare) nu este echivalent cu


declararea unui pointer, deoarece o declaratie de vector aloc memorie si
initializeaza pointerul ce reprezint numele vectorului cu adresa zonei alocate
(operatii care nu au loc automat la declararea unui pointer).
int * a; a[0]=1;
int *a={3,4,5};

// gresit !
// echivalent cu: int a[]={3,4,5}

Nu se poate declara un vector cu componente de tip void. Exemple:


void a[100];
void * a;

// incorect
// corect

Operatorul sizeof aplicat unui nume de vector cu dimensiune fix are ca


rezultat numrul total de octeti ocupati de vector, dar aplicat unui argument
formal de tip vector (sau unui pointer la un vector alocat dinamic) are ca
rezultat mrimea unui pointer:
float x[10], * y=(float*)malloc (10*sizeof(float));
printf (%d,%d \n,sizeof(x),sizeof(y)); // scrie 40,4

Numrul de elemente dintr-un vector alocat la compilare sau initializat cu un


sir de valori se poate afla prin expresia:

http://elth.srv.ro/

63
sizeof (x) / sizeof(x[0])

Pointeri n functii
In definirea functiilor se folosesc pointeri pentru:
- Transmiterea de rezultate prin argumente;
- Transmiterea unei adrese prin rezultatul functiei;
O functie care trebuie s modifice mai multe valori primite prin argumente
sau care trebuie s transmit mai multe rezultate calculate de functie trebuie s
foloseasc argumente de tip pointer.
O functie care primeste un numr si trebuie s modifice acel numr poate
transmite prin rezultatul ei (prin return) valoarea modificat. Exemplu:
// functie care incrementeaza un intreg n modulo m
int incmod (int n, int m ) {
return ++n % m;
}

O functie care primeste dou sau mai multe numere pe care trebuie s le
modifice va avea argumente de tip pointer sau un argument vector care
reuneste toate rezultatele (datele modificate). Exemplu:
// calculeaza urmatorul moment de timp (ora,min,sec)
void inctime (int*h,int*m,int*s) {
*s=incmod(*s,60);
// secunde
if (*s==0) {
*m=incmod(*m,60);
// minute
if (*m==0)
*h=incmod(*h,24);
// ore
}
}
// utilizare functie
void main () {
int h,m,s;
while ( scanf ("%d%d%d",&h,&m,&s) >0) {
inctime (&h,&m,&s);
printf ("%4d%4d%4d \n",h,m,s);
}
}

In exemplul anterior cele trei argumente ntregi pot fi reunite ntr-un vector,
pentru simplificarea functiei:
void inctime (int t[3]) {
t[2]=incmod(t[2],60);

// t[0]=h, t[1]=m, t[2]=s


// secunde

http://elth.srv.ro/

64
if (t[2]==0) {
t[1]=incmod(t[1],60); // minute
if (t[1]==0)
t[0]=incmod(t[0],24); // ore
}
}

O functie poate avea ca rezultat un pointer, dar acest pointer nu trebuie s


contin adresa unei variabile locale. De obicei, rezultatul pointer este egal cu
unul din argumente, eventual modificat n functie. Exemplu:
// incrementare pointer p
char * incptr ( char * p) {
return ++p;
}

O variabil local are o existent temporar, garantat numai pe durata


executrii functiei n care este definit (cu exceptia variabilelor locale statice)
si de aceea adresa unei astfel de variabile nu trebuie transmis n afara
functiei, pentru a fi folosit ulterior. Exemplu gresit:
// vector cu cifrele unui nr intreg
int * cifre (int n) {
int k, c[5];
// vector local
for (k=4;k>=0;k--) {
c[k]=n%10; n=n/10;
}
return c;
// aici este eroarea !
}

Anumite functii cu mai multe rezultate si argumente de tip pointer pot fi


nlocuite prin mai multe functii, fiecare cu un singur rezultat. De exemplu, n
locul functiei urmtoare vom scrie functii separate pentru minim si maxim:
void minmax (int a[], int n, int * min, int* max) {
int i;
*min=INT_MAX; *max = INT_MIN;
for (i=0;i<n;i++){
if (*min > a[i])
*min=a[i];
if (*max < a[i])
*max=a[i];
}
}

O functie care trebuie s transmit ca rezultat un vector poate fi scris corect


n dou feluri:

http://elth.srv.ro/

65
- Primeste ca argument adresa vectorului (definit si alocat n alt functie) si
depune rezultatele la adresa primit (este solutia recomandat). Exemplu:
void cifre (int n, int c[]) {
int k;
for (k=4;k>=0;k--) {
c[k]=n%10; n=n/10;
}
}

- Aloc dinamic memoria pentru vector (cu "malloc"), iar aceast alocare se
mentine si la iesirea din functie. O solutie oarecum echivalent este utilizarea
unui vector local static, care continu s existe dup terminarea functiei.
Functia are ca rezultat adresa vectorului alocat n cadrul functiei.Problema
este unde si cnd se elibereaz memoria alocat. Exemplu:
int * cifre (int n) {
int k, *c;
// vector local
c = (int*) malloc (5*sizeof(int));
for (k=4;k>=0;k--) {
c[k]=n%10; n=n/10;
}
return c;
// corect
}

Pointeri la functii
Anumite aplicatii numerice necesit scrierea unei functii care s poat apela
o functie cu nume necunoscut, dar cu prototip si efect cunoscut. De exemplu,
o functie care s calculeze integrala definit a oricrei functii cu un singur
argument sau care s determine o rdcin real a oricrei ecuatii (neliniare).
Aici vom lua ca exemplu o functie "listf" care poate afisa (lista) valorile unei
alte functii cu un singur argument, ntr-un interval dat si cu un pas dat.
Exemple de utilizare a functiei "listf" pentru afisarea valorilor unor functii de
bibliotec:
void main () {
listf (sin,0.,2.*M_PI, M_PI/10.);
listf (exp,1.,20.,1.);
}

Problemele apar la definirea unei astfel de functii, care primeste ca argument


numele (adresa) unei functii.

http://elth.srv.ro/

66
Prin conventie, n limbajul C, numele unei functii nensotit de o list de
argumente (chiar vid) este interpretat ca un pointer ctre functia respectiv
(fr a se folosi operatorul de adresare '&'). Deci "sin" este adresa functiei
"sin(x)" n apelul functiei "listf".
Declararea unui argument formal (sau unei variabile) de tip pointer la o
functie are forma urmtoare:
tip (*pf) (lista_arg_formale)

unde:
pf este numele argumentului (variabilei) pointer la functie
tip este tipul rezultatului functiei
Parantezele sunt importante, deoarece absenta lor modifica interpretarea
declaratiei. Exemplu de declaratie functie cu rezultat pointer:
tip * f (lista_arg_formale)

In concluzie, definirea functiei "listf" este:


void listf (double (*fp)(double), double min, double
max,
double pas) {
double x,y;
for (x=min; x<=max; x=x+pas) {
y=(*fp)(x);
// sau: y=fp(x);
printf ("\n%20.10lf %20.10lf, x,y);
}
}

O eroare de programare care trece de compilare si se manifest la executie


este apelarea unei functii fr paranteze; compilatorul nu apeleaz functia si
consider c programatorul vrea s foloseasc adresa functiei. Exemplu:
if (kbhit) break;
// gresit, echiv. cu if(1) break;
if (kbhit()) break; // iesire din ciclu la orice tasta

Pentru a face programele mai explicite se pot defini nume de tipuri pentru
tipuri pointeri la functii, folosind declaratia typedef. Exemplu:
typedef double (* ftype) (double);
void listf(ftype fp,double min,double max, double pas) {
double x,y;
for (x=min; x<=max; x=x+pas) {
y=fp(x);
printf ("\n%20.10lf %20.10lf, x,y);
}
}

http://elth.srv.ro/

67
Un vector de pointeri la functii poate fi folosit n locul unui bloc switch
pentru selectarea unei functii dintr-un grup de mai multe functii, ntr-un
program cu meniu de optiuni prin care operatorul alege una din functiile
realizate de programul respectiv. Exemplu:
// functii ptr. operatii realizate de program
void unu() {
printf ("unu\n");
}
void doi() {
printf ("doi\n");
}
void trei() {
printf ("trei\n");
}
// selectare si apel functie
typedef void (*funPtr) ();
void main () {
funPtr tp[]= {unu,doi,trei};
short option=0;
do {
printf(Optiune (1/2/3):);
scanf ("%hd", &option);
if (option >=1 && option <=3)
tp[option-1]();
// apel functie (unu/doi/trei)
} while (1);
}

Secventa echivalent cu switch este :


do {
printf(Optiune (1/2/3):);
scanf ("%hd", &option);
switch (option) {
case 1: unu(); break;
case 2: doi(); break;
case 3: trei(); break;
default: continue;
}
} while (1);

http://elth.srv.ro/

68

7. Operatii cu siruri de caractere n C


Memorarea sirurilor de caractere n C
In limbajul C nu exist un tip de date sir de caractere, desi exist constante
sir (ntre ghilimele). Sirurile de caractere se memoreaz n vectori cu
componente de tip char, dar exist anumite particularitti n lucrul cu siruri
fat de lucrul cu alti vectori.
Sirurile de caractere reprezint nume de persoane, produse, localitti iar
uneori chiar propozitii sau fragmente de texte. Prin natura lor sirurile pot avea
o lungime variabil n limite foarte largi, iar lungimea lor se poate modifica
chiar n cursul executiei unui program ca urmare a unor operatii cum ar fi
concatenarea a dou siruri, stergerea sau inserarea unui subsir ntr-un sir s.a.
Operatiile uzuale cu siruri sunt realizate n C prin functii si nu prin operatori
ai limbajului. O astfel de functie primeste unul sau dou siruri si eventual
produce un alt sir (de obicei sirul rezultat nlocuieste primul sir primit de
functie). Pentru fiecare sir functia ar trebui s primeasc adresa de nceput a
sirului (numele vectorului) si lungimea sa, lungime care se modific la
anumite operatii.
Pentru simplificarea listei de argumente si a utilizrii functiilor pentru
operatii cu siruri s-a decis ca fiecare sir memorat ntr-un vector s fie terminat
cu un octet zero (\0) si s nu se mai transmit explicit lungimea sirului.
Multe functii care produc un nou sir precum si functiile standard de citire
adaug automat un octet terminator la sirul produs (citit), iar functiile care
prelucreaz sau afiseaz siruri detecteaz sfrsitul sirului la primul octet zero.
Citirea unui sir de la tastatur se poate face fie cu functia scanf si
descriptor %s, fie cu functia gets astfel:
- Citirea unei linii care poate include spatii albe se va face cu gets.
- Citirea unui cuvnt (sir delimitat prin spatii albe) se va face cu scanf.
Ambele functii primesc ca argument adresa unde se citeste sirul si nlocuiesc
caracterul \n introdus de la tastatur cu terminatorul de sir (zero).
Exemplu de citire si afisare linii de text, cu numerotare linii:
void main () {
char lin[128]; int nl=0;
// linii de maxim 128
car
while ( gets (lin) != NULL){
printf (%4d ,++nl); puts (lin);
}
}

http://elth.srv.ro/

69
Pentru a determina lungimea unui sir terminat cu zero se poate folosi functia
de bibliotec strlen. Exemplu:
while (scanf (%s,sir) != EOF)
printf (%s %d \n, sir, strlen(sir));

Nu se recomand citirea caracter cu caracter a unui sir, cu descriptorul %c


sau cu functia getchar(), dect dup apelul functiei fflush, care goleste
zona tampon de citire. In caz contrar se citeste caracterul \n (cod 10), care
rmne n zona tampon dup citire cu scanf(%s,..) sau cu getchar().
char b[80]; char c;
scanf("%s",b);
// sau c=getchar(c)
fflush(stdin);
c=getchar();
printf("%d ",c); // scrie 10 daca lipseste fflush

Memorarea unei liste de siruri se poate face ntr-o matrice de caractere n


care fiecare linie din matrice reprezint un sir, dar solutia este ineficient dac
sirurile au lungime foarte variabil, pentru c numrul de coloane din matrice
este determinat de lungimea maxim a unui sir. Exemplu:
char kwords [5][8] =
{"int","char","float","long","double","short"};
// cauta un cuvant in tabelul de cuv cheie
int keyw ( char nume[8], char kw[][8], int n ) {
int i;
for (i=0;i<n;i++)
if (strcmp(nume,kw[i])==0)
return i;
return -1;
}

O solutie care foloseste mai bine memoria este alocarea dinamic de


memorie (la executie) pentru fiecare sir, n functie de lungimea lui si reunirea
adreselor acestor siruri ntr-un vector de pointeri. Solutia corespunde unei
matrice cu linii de lungimi diferite, alocate dinamic.

Erori uzuale la operatii cu siruri de caractere


In descriptorul de format %s se poate specifica o lungime maxim
admisibil pentru sirul citit pentru a preveni o eroare de depsire a memoriei
alocate pentru fiecare sir. Exemplu:
char nume[30];

// lungimea maxima a unui nume

http://elth.srv.ro/

70
scanf (30s, nume);

De retinut c numele unui vector este un pointer si nu mai trebuie aplicat


operatorul & de obtinere a adresei, asa cum este necesar pentru variabile
simple. Exemplu de citire a unui singur caracter (urmat de Enter) n dou
feluri; diferenta dintre ele apare la citirea repetat de caractere individuale.
char c, s[2];
scanf (%c, &c);

scanf(%1s, s);

Poate cea mai frecvent eroare de programare (si care nu se manifest


ntotdeauna ca eroare, la executie) este utilizarea unei variabile pointer
neinitializate n functia scanf (sau gets), datorit confuziei dintre vectori si
pointeri. Exemplu gresit:
char * s;
// corect este: char s[M]; M = lungime
maxima
scanf(%s,s);
//citeste la adresa continuta in s

O alt eroare frecvent (nedetectat la compilare) este compararea adreselor


a dou siruri n locul comparatiei celor dou siruri. Exemplu:
char a[50], b[50]; // aici se memoreaza doua siruri
scanf (%50s%50s, a,b); // citire siruri a si b
if (a==b) printf(egale\n);
//gresit,rezultat zero

Pentru comparare corect de siruri se va folosi functia strcmp. Exemplu :


if (strcmp(a,b)==0)

printf (egale\n);

Aceeasi eroare se poate face si la compararea cu un sir constant. Exemple:


if(nume==.") break; ...}
// gresit !
if(strcmp(nume,.)==0) break;... }
// corect

Din aceeasi categorie de erori face parte atribuirea ntre pointeri cu intentia
de copiere a unui sir la o alt adres, desi o parte din aceste erori pot fi
semnalate la compilare. Exemple:
char a[100], b[100], *c ;
// memorie alocata dinamic la adresa c
c = (char*) malloc(100);
a = b;
// eroare la compilare
// corect sintactic dar nu copiaza sir (modifica
c = a;
// copiaza sir de la adresa a la adresa c

c)

http://elth.srv.ro/

71
strcpy (c,a);
// copiaza la adresa a sirul de la adresa b
strcpy (a,b);

Functiile standard "strcpy" si strcat" adaug automat terminatorul zero la


sfrsitul sirului produs de functie. Functia strncpy nu adaug subsirului
copiat la adresa ss terminatorul de sir atunci cnd pos+len < strlen(s) dar
subsirul este terminat cu zero dac pos+len > strlen(s).

Functii standard pentru operatii cu siruri


Principalele categorii de functii care lucreaz cu siruri de caractere sunt:
- Functii pentru siruri terminate cu zero (siruri complete); numele lor ncepe
cu str.
- Functii pentru subsiruri de lungime maxim; numele lor ncepe cu strn
- Functii pentru operatii cu blocuri de octeti (neterminate cu zero); numele lor
ncepe cu mem.
Aceste functii sunt declarate n fisierele <string.h> si <mem.h>, care trebuie
incluse n compilarea programelor care lucreaz cu siruri de caractere (alturi
de alte fisiere de tip h).
Urmeaz o descriere putin simplificat a celor mai folosite functii standard
pe siruri de caractere.
// strlen: lungimea sirului s ( s terminat cu un octet zero)
int strlen(char * s);
// strcmp: compar sirurile de la adresele s1 si s2
int strcmp (char * s1, char * s2);
// strncmp: compar primele n caractere din sirurile s1 si s2
int strncmp ( char * s1, char * s2, int n);
// copiaz la adresa d tot sirul de la adresa s (inclusiv terminator sir)
char * strcpy (char * d, char * s);
// strncpy: copiaz primele n caractere de la s la d
char * strncpy ( char *d, char * s, int n);
// strcat: adaug sirul s la sfrsitul sirului d
char * strcat (char *d, char* s);
// strncat: adaug primele n car. de la adresa s la sirul d
char * strncat (char *d, char *s, int n);
// strchr: are ca rezultat pozitia lui c n sirul d (prima aparitie a lui c)
char * strchr (char *d, char c);
// caut ultima aparitie a lui c n sirul d
char *strrchr (char *d,char c);
// strstr: are ca rezultat adresa n sirul d a sirului s
char * strstr (char *d, char*s);
// stristr: la fel ca strstr dar nu face diferenta intre litere mici si mari (ignore case)

http://elth.srv.ro/

72
char * strstr (char *d, char*s);

Functiile de comparare siruri au urmtorul rezultat:


== 0 dac sirurile comparate contin aceleasi caractere (sunt identice)
< 0 dac primul sir (s1) este inferior celui de al doilea sir (s2)
> 0 dac primul sir (s1) este superior celui de al doilea sir (s2)
Rezultatul functiei de comparare nu este doar -1, 0 sau 1 ci orice valoare
ntreag cu semn, deoarece comparatia de caractere se face prin scdere.
Exemplu de implementare a functiei strncmp:
int strncmp ( char * d, char *s, int n) {
while ( n>0 && *d && *s && (*d == *s) ) {
n--; ++d; ++s;
}
if (n==0 || (*d==0 && *s==0) )
return 0;
// siruri egale
else
return *d - *s;
}

Se poate defini o functie care s fac mai evident comparatia la egalitate:


// 1 dac siruri egale , 0 dac siruri diferite
int strequ (char * s1, char * s2) {
return strcmp(s1,s2)==0;
}

Functiile de copiere si de concatenare au ca rezultat primul argument (adresa


sirului destinatie) pentru a permite exprimarea mai compact a unor operatii
succesive pe siruri. Exemplu:
int n = strlen (strcat(s1,s2));
char fnume[20], *nume="test", *ext="cpp";
strcat(strcat(strcpy(fnume,nume),"."),ext);

Utilizarea unor siruri constante n operatii de copiere sau de concatenare


poate conduce la erori prin depsirea memoriei alocate (la compilare) sirului
constant. Exemplu gresit :
strcat (test,.cpp);

// eroare la executie

Functiile pentru operatii pe siruri nu pot verifica depsirea memoriei alocate


pentru siruri, deoarece primesc numai adresele sirurilor; cade n sarcina
programatorului s asigure memoria necesar rezultatului unor operatii cu
siruri. Exemplu:

http://elth.srv.ro/

73
char nume[30]="test";
strcat (nume,.cpp);

Definirea de noi functii pe siruri de caractere


Argumentele de functii ce reprezint siruri se declar de obicei ca pointeri
dar se pot declara si ca vectori. Exemple:
// cu pointeri
void strcopy ( char * dst, char * src) {
while ( *dst++ = *src++);
}
// cu vectori
void strcopy ( char dst[], char src[]) {
int k;
for (k=0; src[k]!=0 ; k++)
dst[k]=src[k];
}

Functiile standard pe siruri din C lucreaz numai cu adrese absolute (cu


pointeri) si nu folosesc ca argumente adrese relative n sir (indici ntregi). De
aceea nu exist functii care s elimine un caracter dintr-un sir sau care s
insereze un caracter ntr-un sir sau care s extrag un subsir dintr-o pozitie
dat a unui alt sir.
La definirea unor noi functii pentru operatii pe siruri programatorul trebuie
s asigure adugarea terminatorului de sir la rezultatul functiei, pentru
respectarea conventiei si evitarea unor erori.
Functiile care produc ca rezultat un nou sir modific continutul (si
lungimea) unuia dintre sirurile primite. Aceast solutie poate conduce la erori
prin depsirea memoriei alocate pentru sirul modificat dar producerea unui
nou sir diferit de sirurile primite nu este nici simpl nici sigur. Functia
urmtoare extrage un subsir de lungime dat dintr-o pozitie dat a unui sir:
char* substr(char * str, int pos, int len, char * sstr)
{
if ( pos >= strlen(str) ) return 0 ;
// eroare in
date
strncpy
(sstr,str+pos,len);
//pos=pozitie,len=lungime
sstr[len]=\0;
// adaugare terminator la sstr
return sstr;
// dupa modelul strcpy, strcat
}

Reducerea numrului de argumente (prin eliminarea ultimului argument) sar putea face prin alocare dinamic de memorie pentru subsirul extras:

http://elth.srv.ro/

74

char * substr ( char * str , int pos, int len) {


char * sstr =(char*) malloc(len+1);
//
aloca
memorie
strncpy (sstr,str+pos,len); // extrage in sstr
sstr[len]=\0;
// adaugare terminator la sstr
return sstr;
// adresa sirului rezultat
}

In general nu se scriu functii care s aloce memorie fr s o elibereze,


deoarece apelarea repetat a unor astfel de functii poate duce la consum inutil
de memorie. La fel, nu se admite ca sarcina eliberrii memoriei alocate s
revin celui care apeleaz functia.
O alt solutie (nseltoare) este utilizarea unui vector local cu dimensiune
constant:
char * substr ( char * str , int pos, int len) {
char sstr[1000];
// dimensiune arbitrara
strncpy (sstr,str+pos,len); // extrage in sstr
sstr[len]=\0;
// adaugare terminator
return sstr;
// adresa rezultat
}

In general nu se recomand functii care au ca rezultat adresa unei variabile


locale, desi erorile de utilizare a unor astfel de functii apar numai la apeluri
succesive incluse. Exemple:
puts ( substr(123456,2,3) );
// corect: 345
puts (substr (substr(123456,2,3),1,2) ); // incorect

Pentru realizarea unor noi operatii cu siruri se pot folosi functiile existente.
Exemple:
// sterge n caractere de la adresa d
char * strdel( char *d, int n) {
if ( n < strlen(d))
strcpy(d,d+n);
return d;
}
// insereaza sirul s la adresa d
void strins (char *d, char *s) {
int ld=strlen(d), ls=strlen(s);
strcpy (d+ld+ls,d);
// deplasare dreapta sir d
strcpy(d,s);
strcpy (d+ls, d+ld+ls);
}

http://elth.srv.ro/

75
Observatiiile urmtoare se refer att la functiile standard pe siruri ct si la
functii definite de utilizatori:
- Argumentele sau rezultatele ce reprezint lungimea unui sir sunt de tip
size_t (echivalent de obicei cu unsigned int) si nu int, pentru a permite siruri
de lungime mai mare.
- Argumentele ce reprezint adrese de siruri care nu sunt modificate de
functie se declar astfel:
const char * str

interpretat ca pointer la un sir constant (nemodificabil). Exemplu:


size_t

strlen (const char * s);

Declaratia char const * p este interpretat ca pointer constant la un sir


(caracter) modificabil dar este mult mai rar folosit.

Extragerea de cuvinte dintr-un text


De multe ori se pune problema extragerii de cuvinte dintr-un text. Mai exact,
se extrag atomi lexicali (token este termenul din limba englez tradus prin
atom lexical sau prin cuvnt). Un atom se poate defini n dou feluri:
- un sir de caractere separat de alte siruri printr-un singur caracter separator;
- un sir de caractere separat de alti atomi prin oricare din cteva caractere
separator ;
- un sir care poate contine numai anumite caractere si se termin la primul
caracter interzis.
In primul caz se poate folosi functia de cutare a unui caracter ntr-un sir
strchr (mai complicat) sau functia sscanf cu caracter separator n sirul
format (mai simplu).
In al doilea caz sunt putin separatori de cuvinte si acestia pot fi enumerati.
Pentru extragerea de siruri separate prin spatii albe ( ,\n,\t,\r) se poate
folosi o functie din familia scanf (fscanf pentru citire dintr-un fisier,
sscanf pentru extragere dintr-un sir aflat n memorie). Intre siruri pot fi
oricte spatii albe, care sunt ignorate. Exemplu:
// extragere si afisare cuvinte dintr-o linie de text
void main ( ) {
char cuv[30];
// lungime maxima cuvant=30
while ( scanf (%s,cuv) > 0)
puts (cuv);
// afisare cuvant pe o linie
}

Pentru extragere de cuvinte ce pot fi separate si prin alte caractere (, sau ;


de ex.) se poate folosi functia de biblioteca strtok, ca n exemplul urmtor:

http://elth.srv.ro/

76

void main ( ) {
char linie[128], * cuv; //
char *sep=.,;\t\n
//
gets(linie);
//
cuv=strtok(linie,sep); //
while ( cuv !=NULL) {
puts (cuv);
//
cuv=strtok(0,sep);
//
}
}

adresa
sir de
citire
primul

cuvant in linie
caractere separator
linie
cuvant din linie

scrie cuvant
urmatorul cuvant din linie

Functia strtok are ca rezultat un pointer la urmtorul cuvnt din linie si


adaug un octet zero la sfrsitul acestui cuvnt, dar nu mut la alt adres
cuvintele din text. Acest pointer este o variabil local static n functia
strtok, deci o variabil care si pstreaz valoarea (si adresa) ntre apeluri
succesive.
In al treilea caz sunt mai multi separatori posibili dect caractere admise
ntr-un atom; un exemplu este un sir de cifre zecimale sau un sir de litere (mari
si mici) si separat de alte numere sau nume prin oricare alte caractere.
Extragerea unui sir de cifre sau de litere trebuie realizat de programator, care
poate folosi functiile pentru determinarea tipului de caracter, declarate n
fisierul antet <ctype.h>. Exemplu:
#include <stdio.h>
#include <ctype.h>
// extragere cuvinte formate numai din litere
void main ( ) {
char linie[80], nume[20], *adr=linie; int i;
gets(linie);
while (*adr) {
// ignora alte caractere dect litere
while (*adr && !isalpha(*adr)) ++adr;
if (*adr==0) break;
// daca sfarsit de linie
for (i=0; isalpha(*adr); adr++, i++)
nume[i]=*adr;
// extrage cuvant in nume
nume[i]=0;
// terminator de sir C
puts (nume);
// afiseaza un nume pe o linie
}
}

Cutarea si nlocuirea de siruri


Orice editor de texte permite cutarea tuturor aparitiilor unui sir si, eventual,
nlocuirea lor cu un alt sir, de lungime mai mic sau mai mare. De asemenea,
exist comenzi ale sistemelor de operare pentru cutarea unui sir n unul sau
mai multe fisiere, cu diferite optiuni (comanda Find n MS-DOS).

http://elth.srv.ro/

77
Cutarea de cuvinte complete poate folosi functiile strtok si strcmp, iar
cutarea de subsiruri n orice context (ca prti de cuvinte) poate folosi functia
strstr. In secventa urmtoare se caut si se nlocuiesc toate aparitiile sirului
s1 prin sirul s2 ntr-o linie de text, memorat la adresa line:
while(p=strstr(line,s1)) {
// adresa lui s1 n line
strdel (p,strlen(s1)); // sterge caractere de la adr
p
strins(p,s2);

// insertie de caractere la p

In exemplul anterior am presupus c sirul nou s2 nu contine ca subsir pe s1,


dar mai sigur este ca s mrim adresa din p dup fiecare nlocuire:
p=line;
while(p=strstr (p,s1)) {
strdel (p,strlen(s1));
strins(p,s2);
p=p+ strlen(s2);
}

// cauta un s1 de la p

Sirurile cutate pot contine caracterele wildcards * si ?, cu semnificatia


subsir de orice lungime si orice caractere si respectiv orice caracter.
Anumite functii de bibliotec admit siruri ce contin caractere wildcards;
de exemplu findfirst si findnext pentru cutarea fisierelor al cror nume
se potriveste cu un anumit sir sablon.
In sisteme Unix (Linux) si n anumite limbaje (Perl, Java) operatia de
cutare foloseste o expresie regulat, adic un sablon (o masc) cu care se pot
potrivi mai multe siruri. O expresie regulat este o extindere a unei msti ce
poate contine caractere wildcards. Unele implementri de C ( lcc-win32)
pun la dispozitie functii de bibliotec pentru lucru cu expresii regulate:
regcomp : compileaz o expresie regulat (sir) n structura regexp
regexec : caut ntr-un sir pozitia unui subsir care se potriveste cu o exp. reg.
regsub : substituie ntr-un sir aparitiile unui caracter (&) conform cu ultimul
apel regexec.
In structura regexp exist doi vectori de pointeri (startp si endp) ctre
nceputul si sfrsitul subsirilor care se potrivesc cu expresia regulat. Exemplu
de cutare a oricrei secvente de litere c sau C, repetate:
#include <regexp.h>
#include <stdio.h>
void main () {
char *p;
regexp * r = regcomp("c+|C+");
char txt[]="C++ si cu C; CcccccCe limbaje ! ";

http://elth.srv.ro/

78
p=txt;
// adresa de unde se cauta
do {
regexec (r,p);
// completeaza structura de la r
printf ("%s \n",r->startp[0]);
p= r->endp[0];
// de aici continua cautarea
} while ( r->startp[0]); // startp=NULL daca negasit
}

8. Alocarea dinamica a memoriei n C


Clase de memorare n C
Clasa de memorare arat cnd, cum si unde se aloc memorie pentru o
variabil sau un vector. Orice variabil C are o clas de memorare care rezult
fie dintr-o declaratie explicit, fie implicit din locul unde este definit
variabila.
Exist trei moduri de alocare a memoriei, dar numai dou corepund unor
clase de memorare:
- Static: memoria este alocat la compilare n segmentul de date din cadrul
programului si nu se mai poate modifica n cursul executiei. Variabilele
externe, definite n afara functiilor, sunt implicit statice, dar pot fi declarate
static si variabile locale, definite n cadrul functiilor.
- Automat: memoria este alocat automat, la activarea unei functii, n zona
stiv alocat unui program si este eliberat automat la terminarea functiei.
Variabilele locale unui bloc (unei functii) si argumentele formale sunt implicit
din clasa auto. Memoria se aloc n stiva atasat programului.
- Dinamic: memoria se aloc la executie n zona heap atasat programului,
dar numai la cererea explicit a programatorului, prin apelarea unor functii de
bibliotec (malloc, calloc, realloc). Memoria este eliberat numai la cerere,
prin apelarea functiei free. Variabilele dinamice nu au nume si deci nu se
pune problema clasei de memorare (clasa este atribut al variabilelor cu nume).
Variabilele statice pot fi initializate numai cu valori constante (pentru c are
loc la compilare), dar variabilele auto pot fi initializate cu rezultatul unor
expresii (pentru c are loc la executie). Toate variabilele externe (si statice)
sunt automat initializate cu valori zero (inclusiv vectorii).
Exemplu de utilizare variabil static n functia strtok :
char *strtok (char * sir,char *separ) {
static char *p;
// variabila static !

http://elth.srv.ro/

79
char * r;
if (sir) p=sir;
// ignora separatori intre atomi
while (strchr(separ,*p) && *p )
p++;
if (*p=='\0') return NULL;
r=p;
while (strchr(separ,*p)==NULL && *p)
p++;
if (p==r) return NULL;
else {
*p++='\0'; return r;
}
}

Cantitatea de memorie alocat pentru variabilele cu nume rezult din tipul


variabilei si din dimensiunea declarat pentru vectori. Memoria alocat
dinamic este specificat explicit ca parametru al functiilor de alocare, n
numr de octeti.
O a treia clas de memorare este clasa register pentru variabile crora li se
aloc registre ale procesorului si nu locatii de memorie, pentru un timp de
acces mai bun. Aceast clas nu se va folosi deoarece se las compilatorului
decizia de alocare a registrelor masinii.
Memoria neocupat de datele statice si de instructiunile unui program este
mprtit ntre stiv si heap. Consumul de memorie stack (stiva) este mai
mare n programele cu functii recursive si numr mare de apeluri recursive, iar
consumul de memorie heap este mare n programele cu vectori si matrice
alocate (si realocate) dinamic.

Functii de alocare si eliberare a memoriei


Aceste functii standard sunt declarate n fisierele <alloc.h> si <stdlib.h>.
Cele trei functii de alocare au ca rezultat adresa zonei de memorie alocate (de
tip void *) si ca argument comun dimensiunea zonei de memorie alocate (de
tip "size_t" ). Dac cererea de alocare nu poate fi satisfacut, pentru c nu mai
exista un bloc continuu de dimensiunea solicitat, atunci functiile de alocare
au rezultat NULL.
La apelarea functiilor de alocare se folosesc:
- Operatorul sizeof pentru a determina numrul de octeti necesar unui tip de
date (variabile);
- Operatorul de conversie cast pentru adaptarea adresei primite de la functie
la tipul datelor memorate la adresa respectiva (conversie necesar atribuirii
ntre pointeri de tipuri diferite). Exemple:

http://elth.srv.ro/

80
//aloca memorie pentru 30 de caractere
char * str = (char*) malloc(30);
//aloca memorie ptr. n ntregi
int * a = (int *) malloc( n * sizeof(int));

Alocarea de memorie pentru un vector si initializarea zonei alocate cu


zerouri se poate face si cu functia calloc. Exemplu:
int * a= (int*)calloc (n, sizeof(int) );

Realocarea unui vector care creste (sau scade) fat de dimensiunea estimat
anterior se poate face cu functia realloc, care primeste adresa veche si noua
dimensiune si ntoarce noua adres:
// dublare dimensiune curenta a zonei de la adr. a
a = (int *)realloc (a, 2*n* sizeof(int));

In exemplul anterior noua adres este memorat tot n variabila pointer a,


nlocuind vechea adres (care nu mai este necesar si nici nu mai trebuie
folosit).
Functia realloc realizeaza urmtoarele operatii:
- Aloc o zon de dimensiunea specificat ca al doilea argument.
- Copiaza la noua adres datele de la adresa veche (primul argument al
functiei).
- Elibereaz memoria de la adresa veche.
Functia free are ca argument o adres (un pointer) si elibereaz zona de la
adresa respectiv (alocat prin apelul unei functii ...alloc). Dimensiunea
zonei nu mai trebuie specificat deoarece este memorat la nceputul zonei
alocate (de ctre functia de alocare). Exemplu:
free(a);

Eliberarea memoriei prin "free" este inutil la terminarea unui program,


deoarece nainte de ncrcarea si lansarea n executie a unui nou program se
elibereaz automat toat memoria "heap".

Vectori alocati dinamic


Structura de vector are avantajul simplittii si economiei de memorie fat de
alte structuri de date folosite pentru memorarea unei colectii de date.
Intre cerinta de dimensionare constant a unui vector si generalitatea
programelor care folosesc astfel de vectori exist o contradictie. De cele mai

http://elth.srv.ro/

81
multe ori programele pot afla (din datele citite) dimensiunile vectorilor cu care
lucreaz si deci pot face o alocare dinamic a memoriei pentru acesti vectori.
Aceasta este o solutie mai flexibil, care foloseste mai bine memoria
disponibil si nu impune limitri arbitrare asupra utilizrii unor programe. In
limbajul C nu exist practic nici o diferent ntre utilizarea unui vector cu
dimensiune fix si utilizarea unui vector alocat dinamic, ceea ce ncurajeaz si
mai mult utilizarea unor vectori cu dimensiune variabil.
Un vector alocat dinamic se declar ca variabil pointer care se initializeaz
cu rezultatul functiei de alocare. Tipul variabilei pointer este determinat de
tipul componentelor vectorului.
De observat c nu orice vector cu dimensiune constant este un vector static;
un vector definit ntr-o functie (alta dect main) nu este static deoarece nu
ocup memorie pe toat durata de executie a programului, desi dimensiunea sa
este stabilit la scrierea programului. Un vector definit ntr-o functie este
alocat pe stiv, la activarea functiei, iar memoria ocupat de vector este
eliberat automat la terminarea functiei.
Exemplul urmtor arat cum se poate defini si utiliza un vector alocat
dinamic:
void main() {
int n,i; int * a;
// adresa vector alocat
dinamic
printf ("n="); scanf ("%d", &n);
//
dimensiune
vector
a=(int *) calloc (n,sizeof(int));
// sau:
a=(int*) malloc (n*sizeof(int));
printf ("componente vector: \n");
for (i=0;i<n;i++)
scanf ("%d", &a[i]);
// sau
scanf (%d, a+i);
for (i=0;i<n;i++)
// afisare vector
printf ("%d ",a[i]);
}

Exist si cazuri n care datele memorate ntr-un vector rezult din anumite
prelucrri, iar numrul lor nu poate fi cunoscut de la nceputul executiei. Un
exemplu poate fi un vector cu toate numerele prime mai mici ca o valoare
dat. In acest caz se poate recurge la o realocare dinamic a memoriei. In
exemplul urmtor se citeste un numr necunoscut de valori ntregi ntr-un
vector extensibil:
#define INCR 100
fiecare realocare
void main() {
int n,i,m ;
float x, * v;

//

cu

cat

creste

vectorul

la

// v = adresa vector

http://elth.srv.ro/

82
n=INCR; i=0;
v = (float *)malloc (n*sizeof(float));
//alocare
initiala
while ( scanf("%f",&x) != EOF) {
if (++i == n) {
// daca este necesar
n= n+ INCR;
//creste dimensiune vector
v=(float *) realloc (vector,n*sizeof(float));
}
v[i]=x;
// memorare in vector
}
for (i=0;i<n;i++)
// afisare vector
printf ("%f ",v[i]);
}

Din exemplele anterioare lipseste eliberarea memoriei alocate pentru vectori,


dar fiind vorba de un singur vector alocat n functia main si necesar pe toat
durata de executie, o eliberare final este inutil. Eliberarea explicit poate fi
necesar pentru vectori de lucru, folositi numai n anumite secvente de
program (sau functii).
Realocarea repetat de memorie poate conduce la fragmentarea memoriei
heap, adic la crearea unor blocuri de memorie libere dar neadiacente si prea
mici pentru a mai fi reutilizate ulterior. De aceea, politica de realocare pentru
un vector este uneori dublarea capacittiii sale anterioare.

Vectori de pointeri la date alocate dinamic


Pentru memorarea mai multor siruri de caractere, de lungimi foarte diferite,
este eficient s alocm dinamic memoria pentru fiecare sir, n functie de
lungimea sa. Adresele acestor siruri sunt n general dispersate n memorie.
Mai corect, programatorul nu poate controla modul de alocare si nici adresele
furnizate prin apeluri succesive ale functiei malloc (sau calloc).
Reunirea adreselor sirurilor alocate dinamic se poate face simplu ntr-un
vector de pointeri ctre aceste siruri. Exemplu:
// citire lista siruri, cu vector de pointeri
int readstr ( char * vp[] ) {
int n=0 ;
char * p, sir[80];
while ( scanf ("%s", sir) > 0) {
// citirea unui sir
p= (char*) malloc (strlen(sir)+1); // aloca memorie
strcpy( p,sir); // copiaza sir citit in heap
vp[n]=p; n++; //memoreaza adresa sir in vector
}
return n;
// numar de siruri citite
}
// afisare siruri reunite intr-un vector de pointeri
void printstr ( char * vp[], int n) {
int i;

http://elth.srv.ro/

83
for(i=0;i<n;i++)
printf ("%s\n",vp[i]);
}
// utilizare functii
void main ( ) {
// vector de pointeri cu dimensiune fixa
char * list[100]; int n;
n = readstr(list);
printstr (list, n);
}

Un vector de pointeri alocat dinamic (pentru n siruri) se declar si se


foloseste astfel:
void main () {
int n; char ** list;
// adresa vector de pointeri
printf (n=); scanf (%d,&n); // dimensiune vector
list = (char**) calloc ( n, sizeof (char*));
readstr (list,n); printstr (list,n);
}

Argumente n linia de comand


Functia main poate avea dou argumente, prin care se pot primi date
transmise prin linia de comand ce lanseaz programul n executie. Sistemul
de operare analizeaz linia de comand, extrage cuvintele din linie (siruri
separate prin spatii albe), aloc memorie pentru aceste cuvinte si introduce
adresele lor ntr-un vector de pointeri (alocat dinamic).
Primul argument al functiei main este dimensiunea vectorului de pointeri
(de tip int), iar al doilea argument este adresa vectorului de pointeri (tot un
pointer). Exemplu:
void main ( int argc, char * argv[]) {
int i;
for (i=1;i<n;i++)
// nu se afiseaza si argv[0]
printf (%s , argv[i]);
}

Primul cuvnt, cu adresa n argv[0], este chiar numele programului executat


(numele fisierului ce contine programul executabil), iar celelalte cuvinte din
linie sunt date initiale pentru program: nume de fisiere folosite de program,
optiuni de lucru diverse.
Prelucrarea argumentelor din linia de comand este de obicei secvential si
deci putem renunta la indici n referirea la componentele vectorului de
pointeri. Exemplu:

http://elth.srv.ro/

84
void main ( int argc, char ** argv) {
while (argc --)
printf (%s , *argv++);
}

Modul de interpretare al argumentelor din linia de comand poate depinde


de pozitia lor n lista de argumente si/sau de prezenta unor caractere prefix
(minus de obicei pentru optiuni de lucru). S-a propus chiar un standard POSIX
pentru unificarea modului de interpretare al argumentelor din linia de
comand si exist (sub)programe care prelucreaz aceste argumente conform
standardului.
Matrice alocate dinamic
Alocarea dinamic pentru o matrice este important deoarece:
- Foloseste economic memoria si evit alocri acoperitoare, estimative.
- Permite matrice cu linii de lungimi diferite.
- Reprezint o solutie bun la problema argumentelor de functii de tip
matrice.
Dac programul poate afla numrul efectiv de linii si de coloane al unei
matrice (cu dimensiuni diferite de la o executie la alta), atunci se va aloca
memorie pentru un vector de pointeri (functie de numrul liniilor) si apoi se va
aloca memorie pentru fiecare linie (functie de numrul coloanelor) cu
memorarea adreselor liniilor n vectorul de pointeri. O astfel de matrice se
poate folosi la fel ca o matrice declarat cu dimensiuni constante. Exemplu:
void main () {
int ** a; int i,j,nl,nc;
printf (nr. linii=); scanf (%d,&nl);
printf (nr. col. =); scanf (%d,&nc);
// memorie pentru vectorul de pointeri la linii
a = (int**) malloc (nl*sizeof(int*));
for (i=0; i<nl;i++)
// aloca memorie pentru fiecare linie i
a[i] = (int*) calloc (nc, sizeof(int)); //o linie
// completare diagonala matrice unitate
for (i=0;i<nl;i++)
a[i][i]=1;
// a[i][j]=0 pentru i != j
// afisare matrice
printmat (a,nl,nc);
}

Functia de afisare a matricei se poate defini astfel :


void printmat (int ** a, int nl,int nc) {
for (i=0;i<nl;i++) {

http://elth.srv.ro/

85
for (j=0;j<nc;j++)
printf (%2d, a[i][j]);
printf(\n);
}
}

Notatia a[i][j] este interpretat astfel pentru o matrice alocat dinamic:


a[i] contine un pointer (o adresa b)
b[j] sau b+j contine ntregul din pozitia j a vectorului cu adresa b.
Functia printmat dat anterior nu poate fi apelat dintr-un program care
declar argumentul efectiv ca o matrice cu dimensiuni constante. Exemplul
urmtor este corect sintactic dar nu se execut corect:
void main () {
int x [2][2]={{1,2},{3,4}};
printmat ( (int**)x, 2, 2);
}

// 2 linii si 2 coloane

Explicatia este interpretarea diferit a continutului zonei de la adresa aflat


n primul argument: functia printmat consider c este adresa unui vector de
pointeri ( int * a[]). iar programul principal consider c este adresa unui
vector de vectori (int x[][2]).
Se poate defini si o functie pentru alocarea de memorie la executie pentru o
matrice. Exemplu:
// rezultat adresa matrice sau NULL
int * * intmat ( int nl, int nc) {
int i;
int ** p=(int **) malloc (nl*sizeof (int*));
if ( p != NULL)
for (i=0;i<n;i++)
p[i] =(int*) calloc (nc,sizeof (int));
return p;
}
// utilizare matrice
void main () {
int nl, nc, i,j , ** a, *b;
printf ("nr linii si nr coloane: \n");
scanf ("%d%d", &nl, &nc);
a= intmat(nl,nc);
// completare matrice
b= (int *) a;
for (i=0;i<nl;i++)
for (j=0;j<nc;j++)
*b++= nc*i+j+1;
printmat ((int**)a ,nl,nc);
}

http://elth.srv.ro/

86
De remarcat c pentru matricea alocat dinamic nu mai putem folosi accesul
direct la elemente, pentru c programul compilator nu are informatiile
necesare s calculeze adresa corect a unui element a[i][j]. Exemplu gresit:
// completare matrice
for (i=0;i<nl;i++)
for (j=0;j<nc;j++)
a[i][j] = nc*i+j+1;

9. Tipuri structur n C
Definirea de tipuri si variabile structur
Un tip structur reuneste cteva componente (cmpuri) avnd fiecare un
nume si un tip. Tipurile cmpurilor unei structuri pot fi si sunt n general
diferite. Definirea unui tip structur are sintaxa urmtoare:
struct tag { tip1 c1, tip2 c2, ... };
unde:
tag este un nume de tip folosit numai precedat de cuvntul cheie struct (n
C, dar n C++ se poate folosi singur ca nume de tip).
tip1,tip2,... este tipul unei componente
c1,c2,... este numele unei componente (cmp)
Ordinea enumerrii cmpurilor unei structuri nu este important, deoarece
ne referim la cmpuri prin numele lor. Se poate folosi o singura declaratie de
tip pentru mai multe cmpuri. Exemple:
// momente de timp
struct time {
int ora,min,sec;
};
// o activitate
struct activ {
char numeact[30];
//
struct time start; //
struct time stop;
//
};

(ora,minut,secunda)

nume activitate
ora de incepere
ora de terminare

http://elth.srv.ro/

87
De remarcat c orice declaratie struct se termin obligatoriu cu caracterul
; chiar dac acest caracter urmeaz dup o acolad; aici acoladele nu
delimiteaz un bloc de instructiuni ci fac parte din declaratia struct.
In structuri diferite pot exista cmpuri cu acelasi nume, dar ntr-o aceeasi
structur numele de cmpuri trebuie s fie diferite.
Declararea unor variabile de un tip structur se poate face fie dup
declararea tipului structur, fie simultan cu declararea tipului structur.
Exemple:
struct time t1,t2, t[100]; // t este vector de structuri
struct complex {float re,im;} c1,c2,c3; // numere
complexe
struct complex cv[200];
//
un
vector
de
numere
complexe

Este posibil initializarea la declarare a unor variabile structur. Exemplu:


struct complex c1={1,-1}, c2={2,3};

Astfel de variabile initializate si cu atributul const ar putea fi folosite drept


constante simbolice, n lipsa unor constante numerice de orice tip structur.
Exemplu:
const struct complex i ={0,1};

Printr-o declaratie struct se defineste un nou tip de date de ctre utilizator.


Un tip structur poate fi folosit n :
- declararea de variabile structuri sau pointeri la structuri :
struct time t, * pt;

- declararea unor argumente formale de functii (structuri sau pointeri la


structuri):
void readtime ( struct time * p);
void writetime (struct time t);

- declararea unor functii cu rezultat de un tip structur (mai rar pointer la


structur):
struct time readtime();

Utilizarea tipurilor structur


Operatiile posibile cu variabile de un tip structur sunt:
- atribuirea ntre variabile de acelasi tip structur.
- transmiterea ca argument efectiv la apelarea unei functii.
- transmiterea ca rezultat al unei functii, ntr-o instructiune return.
Ceilalti operatori ai limbajului nu se pot folosi cu operanzi de un tip
structur si trebuie definite functii pentru operatii cu structuri: comparatii,

http://elth.srv.ro/

88
operatii aritmetice, operatii de citire-scriere etc. Exemplul urmtor arat cum
se poate ordona un vector de structuri time, tip definit anterior:
// scrie ora
void wrtime ( struct time t) {
printf ("%02d:%02d:%02d \n", t.ora,t.min,t.sec);
}
// compara momente de timp
int cmptime (struct time t1, struct time t2) {
int h,m;
h=t1.ora-t2.ora;
m=t1.min-t2.min;
if (h) return h;
// <0 daca t1<t2 si >0 daca t1>t2
if (m) return m;
// rezultat negativ sau pozitiv
return t1.sec-t2.sec;
// rezultat <0 sau =0 sau > 0
}
void main () {
struct time tab[200], aux; int i,j,n;
. . .
// citire date
// ordonare vector
for (j=1;j<n;j++)
for (i=1;i<n;i++)
if ( cmptime (tab[i-1],tab[i]) > 0) {
aux=tab[i-1]; tab[i-1]=tab[i]; tab[i]=aux;
}
// afisare lista de date
for (i=0;i<n;i++)
wrtime(tab[i]);
}

Cmpurile unei variabile structur nu se pot folosi dect dac numele


cmpului este precedat de numele variabilei structur din care face parte,
deoarece exist un cmp cu acelasi nume n toate variabilele de un acelasi tip
structur. Exemplu:
void main () {
struct complex c1,c2;
scanf (%f%f", &c1.re, &c1.im);
// citire c1
c2.re=c1.re; c2.im=-c1.im;
// complex conjugat
printf ((%f,%f) , c2.re, c2.im); // scrie c2
}

Dac un cmp este la rndul lui o structur, atunci numele unui cmp poate
contine mai multe puncte ce separ numele variabilei si cmpurilor de care
apartine (n ordine ierarhic). Exemplu:
struct activ a;
printf (%s ncepe la %d: %d si se termina la %d: %d \n,
a.numeact, a.start.ora, a.start.min,

http://elth.srv.ro/

89
a.stop.ora, a.stop.min);

In cazul variabilelor structur alocate dinamic si care nu au nume se poate


folosi fie indirectarea printr-un pointer pentru a ajunge la variabila structur,
fie o notatie prescurtat ce foloseste dou caractere separator ntre numele
variabilei pointer si numele cmpului. Exemplu:
struct time * pt;
pt = (struct date*) malloc (sizeof(struct date));
printf (%d:%d:%d , pt->ora, pt->min, pt->sec);

Notatiile urmtoare sunt absolut echivalente:


pt->ora

(*pt).ora

Principalele avantaje ale utilizrii unor tipuri structur sunt:


- Anumite programe devin mai explicite dac se folosesc structuri n locul
unor variabile separate.
- Se pot defini tipuri de date specifice aplicatiei iar programul reflect mai
bine universul aplicatiei.
- Se poate reduce numrul de argumente al unor functii prin gruparea lor n
argumente de tipuri structur si deci se simplific utilizarea acelor functii.
- Se pot utiliza structuri de date dinamice, extensibile, formate din variabile
structur alocate dinamic si legate ntre ele prin pointeri (liste nlntuite, arbori
s.a).

Functii cu argumente si rezultat structur


Operatiile cu variabile structur se realizeaz prin functii, definite de
utilizator. Exemplu:
// afisare numar complex
void writex ( struct complex c) {
printf ((%.2f,%.2f) , c.re, c.im);
}

O functie care produce un rezultat de un tip structur poate fi scris n dou


moduri, care implic si utilizri diferite ale functiei.
In exemplul urmtor functia are rezultat de tip structur:
// citire numar complex (varianta 1)
struct complex readx () {
struct complex c;

http://elth.srv.ro/

90
scanf (%f%f,&c.re, &c.im);
return c;
}
. . . // utilizare
struct complex a[100]; . . .
for (i=0;i<n;i++)
a[i]=readx();

In exemplul urmtor functia este de tip void si depune rezultatul la adresa


primit ca argument (pointer la tip structur):
// citire numar complex (varianta 2)
void readx ( struct complex * px) {
scanf (%f%f, &px->re, &px->im);
}
. . . // utilizare
struct complex a[100]; . . .
for (i=0;i<n;i++)
readx (&a[i]);

Uneori mai multe variabile descriu mpreun un anumit obiect de date si


trebuie transmise la functiile ce lucreaz cu obiecte de tipul respectiv.
Gruparea acestor variabile ntr-o structur va reduce numrul de argumente si
va simplifica apelarea functiilor. Exemple de obiecte definite prin mai multe
variabile: obiecte geometrice (puncte, poligoane s.a), date calendaristice si
momente de timp, structuri de date (stiva, coada, s.a), vectori, matrice, etc.
Exemplu de grupare ntr-o structur a adresei si dimensiunii unui vector:
typedef struct {
int vec[1000];
int dim;
};
// afisare vector
void scrvec (vector v) {
int i;
for (i=0;i<v.dim;i++)
printf ("%d ",v.vec[i]);
printf ("\n");
}
// extrage elemente comune din doi vectori
vector comun(vector a, vector b) {
vector c; int i,j,k=0;
for (i=0;i<a.dim;i++)
for (j=0;j<b.dim;j++)
if (a.vec[i]==b.vec[j])
c.vec[k++]=a.vec[i];
c.dim=k;
return c;
}

http://elth.srv.ro/

91
De remarcat c o functie nu poate avea ca rezultat direct un vector, dar poate
avea ca rezultat o structur care include un vector.

Definirea unor noi tipuri de date


Declaratia typedef din C permite definirea unui nume de tip, care se poate
folosi apoi la fel cu tipurile predefinite ale limbajului. Sintaxa declaratiei
typedef este la fel cu sintaxa unei declaratii de variabil, dar se declar un
nume de tip si nu un nume de variabil. Exemple:
typedef unsigned char byte; byte a[20][20];
typedef int logic;
logic gata, corect;
typedef
int
intvec[200];
intvec
a,b;
a[200],b[200];
typedef char * string;
string s1,s2,s3;
typedef void * pointer;
pointer p;

//int

In limbajul C declaratia typedef se utilizeaz frecvent pentru atribuirea de


nume unor tipuri structur. Exemple:
// definire
typedef struct
// definire
typedef struct

nume tip odat cu definire tip structur


{ float re,im;} complex;
nume tip dup definire tip structura
activ act;

Utilizarea unor nume de structuri permite utilizatorilor extinderea limbajului


cu noi tipuri de date mai adecvate problemei rezolvate. Exemplu:
typedef double real;
typedef struct { real x,y;} punct;
typedef struct { int nv; punct v[50];} poligon;
// lungime segment delimitat de doua puncte
real lung (punct a, punct b) {
real dx= b.x-a.x;
real dy= b.y-a.y;
return sqrt ( dx*dx+dy*dy);
}
// calcul primetru poligon
real perim ( poligon p) {
int i,n; real rez=0;
n=p.nv;
for (i=0;i<n-1;i++)
rez = rez + lung (p.v[i],p.v[i+1]);
return rez+lung(p.v[n-1],p.v[0]);
}

http://elth.srv.ro/

92
Atunci cnd numele unui tip structur este folosit frecvent, inclusiv n
argumente de functii, este preferabil un nume introdus prin typedef, dar dac
vrem s punem n evident c este vorba de tipuri structur vom folosi numele
precedat de cuvntul cheie struct.

Structuri cu continut variabil


Cuvntul cheie union se foloseste la fel cu struct, dar defineste un grup de
variabile care nu se memoreaz simultan ci alternativ. In felul acesta se pot
memora diverse tipuri de date la o aceeasi adres de memorie. Alocarea de
memorie se face (de ctre compilator) n functie de variabila ce necesit
maxim de memorie. O uniune face parte de obicei dintr-o structur care mai
contine si un cmp discriminant, care specific tipul datelor memorate
(alternativa selectat la un moment dat). Exemplul urmtor arat cum se poate
lucra cu numere de diferite tipuri si lungimi, reunite ntr-un tip generic :
// numar de orice tip
struct numar {
char tipn;
// tip numar (un caracter)
union {
int ival; long lval; float fval; double dval;
} v;
};
// afisare numar
void write (struct numar n) {
switch (n.tipn) {
case 'i': printf ("%d ",n.v.ival);break;
case 'l': printf ("%ld ",n.v.lval);break;
case 'f': printf ("%f ",n.v.fval);break;
case 'd': printf ("%.15lf ",n.v.dval);break;
}
}
void main () {
struct numar a,b,c,d;
a = read('i'); b=read('l');
c = read('f'); d=read('d');
write(a); write(b); write(c); write(d);
}

Pentru cmpul discriminat se poate defini un tip prin enumerare, mpreun


cu valorile constante (simbolice) pe care le poate avea. Exemplu:
enum tnum {I,L,F,D} ;
// definire tip tnum
struct numar {
tnum tipn;
// valori posibile: I,L,F,D
. . .

http://elth.srv.ro/

93
};
void write (struct numar n) {
switch (n.tipn) {
case I : printf ("%d ",n.v.ival);break; // I=0
...
}
struct numar read (tnum tip) {
struct numar n;
n.tipn=tip;
switch (tip) {
case I: scanf ("%d", &n.v.ival);break;
. . .
}
return n;
}
void main () {
struct numar a,b,c,d;
a = read(I); write(a);
...
}

In locul constructiei union se poate folosi o variabil de tip void* care va


contine adresa unui numr, indiferent de tipul lui. Memoria pentru numr se
va aloca dinamic. Exemplu:
enum tnum {I,L,F,D} ;
struct number {
tnum tipn;
// tip numar
void * pv;
// adresa numar
};
// afisare numar
void write (number n) {
switch (n.tipn) {
case I: printf ("%d ", *(int*) n.pv);break;
...
case D: printf ("%.15lf ",*(double*) n.pv);break;
}
}

Structuri predefinite
Anumite functii de bibliotec folosesc tipuri structur definite n fisiere de
tip H si care ascund detalii ce nu intereseaz pe utilizatori. Un exemplu este
tipul FILE definit n "stdio.h" si a crui definitie depinde de implementare si
de sistemul de operare gazd (o structur opac, invizibil pentru utilizator).
Structura struct tm definit n fisierul <time.h> contine componentele ce
definesc complet un moment de timp:

http://elth.srv.ro/

94
struct tm {
int tm_sec, tm_min, tm_hour;
int tm_mday, tm_mon, tm_year;
int tm_wday, tm_yday;
int tm_isdst;
};

Exemplul urmtor arat cum se poate afisa ora si ziua curent, folosind
numai functii standard:
#include <stdio.h>
#include <time.h>
void main(void) {
time_t t;
// time_t = long
struct tm *area;
t = time(NULL);
// obtine ora curenta
area = localtime(&t); // conv. din time_t in struct tm
printf("Local time is: %s", asctime(area));
}

Structura struct stat este definit n fisierul <sys/stat.h> si reuneste date


despre un fisier, cu exceptia numelui. O parte din informatii sunt valabile
numai pentru sisteme de tip Unix si sunt necomentate n definitia urmtoare:
struct stat {
unsigned short st_dev; // daca fisier dispozitiv
unsigned short st_ino;
unsigned short st_mode; // atribute fisier
short st_nlink,st_uid, st_gid;
unsigned long st_rdev;
long st_size;
// dimensiune fisier (octeti)
long st_atime, st_mtime; // ultimul acces / modific
long st_ctime;
// data de creare
};

Functia stat completeaz o astfel de structur pentru un fisier cu nume dat:


int stat (char* filename, struct stat * p);

Structuri legate prin pointeri


Variabilele dinamice, create prin apeluri repetate ale functiei "malloc" sunt
plasate la adrese necontrolabile, n general neadiacente. Dac ntre aceste
variabile exist o asociere, relatiile dintre ele pot fi memorate explicit prin
pointeri.

http://elth.srv.ro/

95
O list nlantuit (linked list) este o colectie de variabile alocate dinamic
(de acelasi tip), dispersate n memorie, dar legate ntre ele prin pointeri, ca
ntr-un lant. Intr-o list liniar simplu nlntuit fiecare element al listei
contine adresa elementului urmtor din list. Ultimul element poate contine ca
adres de legatur fie constanta NULL, fie adresa primului element din list
(lista circular). Adresa primului element din list este memorat ntr-o
variabil cu nume (alocat la compilare) si numit cap de lista (list head).
Pentru o list vid variabila cap de list este NULL.
Structura de list este recomandat atunci cnd colectia de elemente are un
continut foarte variabil (pe parcursul executiei) sau cnd trebuie pstrate mai
multe liste cu continut foarte variabil.
Un element din list (un nod de list) este de un tip structur si are (cel
putin) dou cmpuri: un cmp de date (sau mai multe) si un cmp de legatur.
Exemplu pentru o list de ntregi:
typedef struct snod {
int val ;
struct snod * leg ;
} nod;

// camp de date
// camp de legatura

Programul urmtor arat cum se poate crea si afisa o list cu adugare la


nceput (o stiv):
void main ( ) {
nod *lst=NULL, *nou, * p; // lst = adresa cap de lista
int x;
// creare lista cu numere citite
while (scanf("%d",&x) > 0) {
// citire numar intreg x
nou=(nod*)malloc(sizeof(nod));
// creare nod nou
nou->val=x;
// completare camp de date din nod
nou->leg=lst; lst=nou; // legare nod nou la lista
}
// afisare list (fara modificare cap de lista)
p=lst;
while ( p != NULL) {
// cat mai sunt noduri
printf("%d ", p->val); // afisare numar de la adr p
p=p->leg;
// avans la nodul urmator
}
}

Cmpul de date poate fi la rndul lui o structur specific aplicatiei sau


poate fi un pointer la date alocate dinamic (un sir de caractere, de exemplu).
De obicei se definesc functii pentru operatiile uzuale cu liste. Exemple:
typedef struct snod {
int val;
struct snod *leg;

http://elth.srv.ro/

96
} nod,* pnod, * list;
void printL ( list lst) {
// afisare lista - recursiv
if (lst != NULL) {
printf("%d ",lst->val);
printL (lst->leg);
}
}
list insL( list lst, int x){ // insertie la inceput lista
pnod nou ;
// adresa nod nou
if ((nou=(list)malloc(sizeof(nod))) ==NULL)
return NULL;
nou->val=x; nou->leg=lst;
return nou;
}
void main (){
// creare si afisare lista stiva
list st; int x;
st=NULL;
while (scanf("%d",&x) > 0)
st=insL(st,x);
printL (st);
}

Alte structuri dinamice folosesc cte doi pointeri; ntr-o list dublu nlntuit
fiecare nod contine adresa nodului urmtor si adresa nodului precedent.
10. Fisiere de date n C
Tipuri de fisiere
Un fisier ("File") este o colectie de date memorate pe un suport extern si
care este identificat printr-un nume. Fisierele se folosesc fie pentru date
initiale si pentru rezultate mai numeroase, fie pentru pstrarea de durat a unor
date de interes pentru anumite aplicatii.
Fisierele sunt entitti ale sistemului de operare si ca atare ele au nume care
respect conventiile sistemului, fr legtur cu un limbaj de programare.
Operatiile cu fisiere sunt realizate de ctre sistemul de operare, iar
compilatorul unui limbaj traduce functiile (instructiunile) de acces la fisiere n
apeluri ale functiilor sistem.
Programatorul se refer la un fisier printr-o variabil; tipul acestei variabile
depinde de limbajul folosit si chiar de functiile utilizate (n C). Asocierea
dintre numele extern (un sir de caractere) si variabila din program se face la
deschiderea unui fisier, printr-o functie standard.
De obicei prin "fisier" se subntelege un fisier disc (pe suport magnetic sau
optic), dar notiunea de fisier este mai general si include orice flux de date din
exterior spre memorie sau dinspre memoria intern spre exterior. Dispozitivele
periferice uzuale au nume de fisiere predefinite; de exemplu, n limbajul C sub
MS-DOS si MS-Windows se pot folosi urmtoarele nume :

http://elth.srv.ro/

97
CON = consola sistem (tastaura la citire si monitor la scriere)
PRN (LPT) = imprimanta sistem
Pentru fisierele disc un nume de fisier poate include urmtoarele:
- Numele unittii de disc sau partitiei disc ( ex: A:, C:, D:, E:)
- "Calea" spre fisier, care este o succesiune de nume de fisiere catalog
(director), separate printr-un caracter ('\' n MS-DOS si MS-Windows, sau '/' n
Unix si Linux)
- Numele propriu-zis al fisierului ( max 8 litere si cifre n MS-DOS)
- Extensia numelui, care indic tipul fisierului (continutul su) si care poate
avea ntre 0 si 3 caractere n MS-DOS).
Exemple de nume de fisiere disc:
A:bc.rar , c:\borlandc\bin\bc.exe
c:\work\p1.cpp , c:\work\p1.obj

Sistemele MS-DOS si MS-Windows nu fac deosebire ntre litere mari si


litere mici, n cadrul numelor de fisiere, dar sistemele de tip Unix sau Linux
fac deosebire ntre litere mari si litere mici.
Consola si imprimanta sunt considerate fisiere text, adic:
- ntre aceste fisiere si memorie se transfer caractere ASCII
- se recunoaste caracterul sfrsit de fisier (Ctrl-Z n MS-DOS si MSWindows, Ctrl-D n Unix)
- se poate recunoaste la citire un caracter terminator de linie ('\n').
Un fisier text pe disc contine numai caractere ASCII, grupate n linii si este
terminat printr-un caracter terminator de fisier (Ctrl-Z), adugat automat la
nchiderea fisierului, dup scriere n fisier.
Functiile de citire sau de scriere numere din/in fisiere text realizeaz
conversia automat din format extern (sir de caractere) n format intern (binar
virgul fix sau virgul mobil) la citire si conversia din format intern n
format extern, la scriere.
Fisierele disc pot contine si numere n reprezentare intern (binar) sau alte
date ce nu reprezint numere (de exemplu, fisiere cu imagini grafice, n
diverse formate). Aceste fisiere se numesc fisiere binare, iar citirea si scrierea
se fac fr conversie de format. Pentru fiecare tip de fisier binar este necesar
un program care s cunoasc si s interpreteze corect informatiile binare din
fisier.
Fisierele disc trebuie deschise si nchise, dar fisierele consol si imprimanta
nu trebuie deschise si nchise.

Functii pentru deschidere si nchidere fisiere.

http://elth.srv.ro/

98

In C sunt disponibile dou categorii de functii pentru acces la fisiere:


- Functii stil Unix, declarate n fisierul io.h si care se refer la fisiere prin
numere ntregi.
- Functii standard, declarate n fisierul stdio.h si care se refer la fisiere
prin pointeri la o structur predefinit ("FILE").
In continuare vor fi prezentate numai functiile standard, din <stdio.h>.
Pentru a citi sau scrie dintr-un /ntr-un fisier disc, acesta trebuie mai nti
deschis folosind functia "fopen". La deschidere se precizeaz numele
fisierului, tipul de fisier (text/binar) si modul de exploatare: numai citire,
numai scriere (creare) sau citire si scriere (modificare).
La deschiderea unui fisier se initializeaz variabila pointer asociat, iar
celelalte functii (de acces si de nchidere) se refer la fisier numai prin
intermediul variabilei pointer. Exemplu:
#include <stdio.h>
void main ( ) {
FILE * f;
// pentru referire la
fisier
// deschide un fisier text ptr citire
f = fopen ("t.txt","rt");
printf ( f == NULL? "Fisier negasit" : " Fisier
gasit");
if (f)
// daca fisier existent
fclose(f);
// inchide fisier
}

Functia "fopen" are rezultat NULL (0) dac fisierul specificat nu este gsit
dup cutare n directorul curent sau pe calea specificat sau pe cile de
cutare definite n sistem (prin comanda PATH ).
Primul argument al functiei "fopen" este numele extern al fisierului scris cu
respectarea conventiilor limbajului C: pentru separarea numelor de cataloage
dintr-o cale se vor folosi dou caractere "\\", pentru a nu se considera o
secvent de caractere "Escape" a limbajului. Exemple:
FILE * f = fopen ("c:\\work\\t.txt", "r");
// varianta la linia anterioara:
char *numef = "C:\\WORK\\T.TXT";
FILE * f;
if ( (f=fopen(numef,"r")) == NULL){
printf("Eroare la deschidere fisier %s \n", numef);
return;
}

http://elth.srv.ro/

99
Al doilea argument al functiei "fopen" este un sir care poate contine ntre 1
si 3 caractere, dintre urmtoarele caractere posibile:
"r,"w","a" = mod de folosire ("read", "write", "append")
"+" dup "r" sau "a" pentru citire si scriere din acelasi fisier
"t" sau "b" = tip fisier ("text", "binary"), implicit este "t"
Pentru fisierele text sunt folosite modurile "w" pentru crearea unui nou
fisier, "r" pentru citirea dintr-un fisier si "a" pentru adugare la sfrsitul unui
fisier existent. Pentru actualizarea unui fisier text prin modificarea lungimii
unor linii, stergerea sau insertia de linii se va scrie un alt fisier si nu se vor
opera modificrile direct pe fisierul initial.
Pentru fisierele binare se practic actualizarea pe loc a fisierelor, fr
inserarea de date ntre cele existente, deci modurile "r+","a+","w+". (literele 'r'
si 'w' nu pot fi folosite simultan).
Inchiderea unui fisier disc este absolut necesar pentru fisierele n care s-a
scris ceva, dar poate lipsi dac s-au facut doar citiri de date din fisier.
Fisierele standard de intrare-iesire (tastatura si ecranul consolei) au asociate
variabile de tip pointer cu nume predefinit ("stdin" si "stdout"), care pot fi
folosite n diferite functii, dar practic se folosesc numai in functia "fflush" care
goleste zona tampon ("buffer") asociat unui fisier.
Operatiile de stergere a unui fisier existent ("remove") si de schimbare a
numelui unui fisier existent ("rename") nu necesit deschiderea fisierelor.

Functii de citire-scriere n fisiere text


Accesul la fisiere text ce contin programe surs sau documentatii se poate
face fie la nivel de linie, fie la nivel de caracter, dar numai secvential. Deci nu
se pot citi/scrie linii sau caractere dect n ordinea memorrii lor n fisier si nu
pe srite (aleator).
Pentru citire/scriere din/n fisierele standard se folosesc functii cu nume
putin diferit si cu mai putine argumente, dar se pot folosi si functiile generale
destinate fisierelor disc cu orice nume. Urmeaz cteva perechi de functii
echivalente ca efect :
// citire caracter
int fgetc (FILE * f);
int getchar();
// echiv. cu fgetc(stdin)
// scriere caracter
int fputc (int c, FILE * f);
int putchar (int c);
//
echiv.
fputc(c.stdout)
// citire linie
char * fgets( char * line, int max, FILE *f);

cu

http://elth.srv.ro/

100
char * gets (char * line);
// scriere linie
int fputs (char * line, FILE *f);
int puts (char * line);

Functia "fgets" adaug sirului citit un octet zero (n memorie), iar functia
"fputs" nu scrie n fisier octetul zero (necesar numai n memorie).
Primul exemplu este un program care citeste un fisier text si afiseaz
continutul su la imprimant:
// listare fisier text la imprimanta
void main () {
char numef[100], linie[132];
FILE * txt, *lst;
puts ("Nume fisier:"); gets (numef);
txt = fopen(numef,"r");
lst = fopen ("PRN","w"); // poat lipsi ptr. stdprn
while (fgets (linie,132,txt))
fputs (linie,lst);
// fputs(linie,stdprn);
fclose(txt);
}

Exemplul urmtor citeste un fisier text si scrie un alt fisier n care toate
literele mari din textul citit sunt transformate n litere mari.
// copiere fisier cu transformare in litere mari
void main (int argc, char * argv[]) {
FILE * f1, * f2; int ch;
f1= fopen (argv[1],"r");
f2= fopen (argv[2],"w");
if ( f1==0 || f2==0) {
puts (" Eroare la deschidere fisiere \n");
return;
}
while ( (ch=fgetc(f1)) != EOF)
// citeste din f1
fputc ( tolower(ch),f2);
// scrie in f2
fclose(f1); fclose(f2);
}

Functii de citire-scriere cu format


Datele numerice pot fi scrise n fisiere disc fie n format intern (mai
compact), fie transformate n siruri de caractere (cifre zecimale, semn s.a).
Formatul sir de caractere necesit si caractere separator ntre numere, ocup
mai mult spatiu dar poate fi citit cu programe scrise n orice limbaj sau cu
orice editor de texte sau cu alt program utilitar de vizualizare fisiere.
Functiile de citire-scriere cu conversie de format si editare sunt:
int fscanf (FILE * f, char * fmt, ...)

http://elth.srv.ro/

101
int fprintf (FILE * f, char * fmt, ...)

Pentru aceste functii se aplic toate regulile de la functiile "scanf" si "printf".


Un fisier text prelucrat cu functiile "fprintf" si "fscanf" contine mai multe
cmpuri de date separate ntre ele prin unul sau mai multe spatii albe (blanc,
tab, linie nou). Continutul cmpului de date este scris si interpretat la citire
conform specificatorului de format pentru acel cmp .
Exemplu de creare si citire fisier de numere.
// creare - citire fisier text ce contine doar numere
void main () {
FILE * f; int x;
// f = pointer la fisier
// creare fisier de date
f=fopen ("num.txt","w");
// deschide fisier
for (x=1;x<=100;x++)
fprintf (f,"%4d",x);
// scrie un numar
fclose (f);
// inchidere fisier
// citire si afisare fisier creat
f=fopen ("num.txt","r");
while (fscanf (f,"%d",&x) > 0) //pana
la
sfirsit
fisier
printf ("%4d",x);
// afisare numar citit
}

Uneori poate fi util functia fflush (FILE*) care goleste zona tampon
folosit la citire sau scriere si care nu este direct accesibil prin numele su.
Fisiere text cu numere se folosesc pentru fisiere de date initiale cu care se
verific anumite programe, n faza de punere la punct. Rezultatele unui
program pot fi puse ntr-un fisier fie pentru a fi prelucrate de un alt program,
fie pentru arhivare sau pentru imprimare repetat.
De observat ca majoritatea sistemelor de operare permit redirectarea
fisierelor standard de intrare si de iesire, fr a modifica programele. Deci un
program (neinteractiv) care foloseste functiile "scanf" si "printf" sau alte
functii standard (gets, puts, getchar, putchar) poate s-si citeasc datele dintrun fisier sau s scrie rezultatele ntr-un fisier prin specificarea acestor fisiere n
linia de comanda. Exemple de utilizare a unui program de sortare:
date de la tastatura,rezultate afisate pe ecran
sort
date din "input", rezultate in "output"
sort <<input >>output
date de la tastatura, rezultate in "output"
sort >>output
date din "input",rezultate afisate pe ecran
sort <<input

http://elth.srv.ro/

102
Functii de acces secvential la fisiere binare
Un fisier binar este format n general din articole de lungime fix, fr
separatori ntre articole. Un articol poate contine un singur octet sau un numr
binar (pe 2,4 sau 8 octeti) sau o structur cu date de diferite tipuri.
Functiile de acces pentru fisiere binare "fread" si "fwrite" pot citi sau scrie
unul sau mai multe articole, la fiecare apelare. Transferul ntre memorie si
suportul extern se face fr conversie sau editare (adugare de caractere la
scriere sau eliminare de caractere la citire).
Programul urmtor scrie mai multe numere ntregi ntr-un fisier disc si apoi
citeste continutul fisierului si afiseaz pe ecran numerele citite.
void main () {
FILE * f; int x;
// creare fisier
f=fopen ("num.bin","wb");
// din
curent
for (x=1; x<=100; x++)
fwrite (&x,sizeof(float),1,f);
fclose(f);
// citire fisier pentru verificare
printf("\n");
f=fopen ("num.bin","rb");
while (fread (&x,sizeof(float),1,f)==1)
printf ("%4d ",x);
fclose(f);
}

directorul

Lungimea fisierului "num.bin" este de 200 de octeti, cte 2 octeti pentru


fiecare numr ntreg, n timp ce lungimea fisierului "num.txt" creat anterior cu
functia "fprintf" este de 400 de octeti (cte 4 caractere ptr fiecare numr).
Pentru alte tipuri de numere diferenta poate fi mult mai mare.
De remarcat c primul argument al functiilor "fread" si "fwrite" este o adres
de memorie (un pointer): adresa unde se citesc date din fisier sau de unde se
iau datele scrise n fisier. Al doilea argument este numrul de octeti pentru un
articol, iar al treilea argument este numrul de articole citite sau scrise.
Numrul de octeti cititi sau scrisi este egal cu produsul dintre lungimea unui
articol si numrul de articole.
Rezultatul functiilor "fread" si "fwrite" este numrul de articole efectiv citite
sau scrise si este diferit de argumentul 3 numai la sfrsit de fisier (la citire) sau
n caz de eroare de citire/scriere.
Functiile din exemplul urmtor scriu sau citesc articole ce corespund unor
variabile structur :
// operatii cu un fisier de elevi (nume si medie)
typedef struct {

http://elth.srv.ro/

103
char nume[25];
float medie;
} Elev;
// creare fisier cu nume dat
void creare(char * numef) {
FILE * f; Elev s;
f=fopen(numef,"wb"); assert (f != NULL);
printf (" nume si medie ptr. fiecare student : \n\n");
while (scanf ("%s %f ", s.nume, &s.medie) != EOF)
fwrite(&s,sizeof(s),1,f);
fclose (f);
}
// afisare continut fisier pe ecran
void listare (char* numef) {
FILE * f; Elev e;
f=fopen(numef,"rb"); assert (f != NULL);
while (fread (&e,sizeof(e),1,f)==1)
printf ("%-25s %6.2f \n",e.nume, e.medie);
fclose (f);
}
// adaugare articole la sfarsitul unui fisier existent
void adaugare (char * numef) {
FILE * f; Elev e;
f=fopen(numef,"ab"); assert (f != NULL);
printf (" nume si medie ptr. fiecare student : \n\n");
while (scanf ("%s%f ",e.nume, &e.medie) != EOF)
fwrite(&e,sizeof(e),1,f);
fclose (f);
}

Functii pentru acces direct la date


Accesul direct la date dintr-un fisier este posibil numai pentru un fisier cu
articole de lungime fix si nseamn posibilitatea de a citi sau scrie oriunde
ntr-un fisier, printr-o pozitionare prealabil nainte de citire sau scriere. In C
pozitionarea se face la un anumit octet din fisier, deci functiile standard permit
accesul direct la o anumit adres de octet (pozitie) din fisier.
Accesul direct este necesar n operatii de cutare dup continut (de ex.
cutare elev dup nume). Functiile standard C pentru acces direct permit
operatiile urmtoare:
- Pozitionarea pe un anumit octet din fisier ("fseek").
- Citirea pozitiei curente din fisier ("ftell").
- Memorarea pozitiei curente si pozitionare ("fgetpos", "fsetpos").
Pozitia curent n fisier este un numr de tip long, pentru a permite operatii
cu fisiere foarte lungi.
Functia "fseek" are prototipul urmtor :

http://elth.srv.ro/

104
int fseek (FILE * f, long bytes, int origin);

unde "bytes" este numrul de octeti fat de punctul de referint "origin", care
poate fi: 0 = nceputul fisierului, 1 = pozitia curent, 2 = sfrsitul fisierului.
Functia "fseek" este util n urmtoarele situatii:
- Pentru repozitionare pe nceput de fisier dup o cutare si nainte de o alt
cutare secvential n fisier (fr a nchide si a redeschide fisierul)
- Pentru pozitionare pe nceputul ultimului articol citit, n vederea scrierii
noului continut (modificat) al acestui articol, deoarece orice operatie de citire
sau scriere avanseaz automat pozitia curent n fisier, pe urmtorul articol.
In exemplul urmtor sunt ilustrate ambele situatii:
// modificare continut articole, dupa cautarea lor
void modificare (char * numef) {
FILE * f; Elev e; char nume[25];
long pos; int eof;
f=fopen(numef,"rb+"); assert (f != NULL);
do {
printf ("Nume cautat: "); eof=scanf ("%s",nume);
if (eof==EOF) break;
// cauta "nume" in fisier
fseek(f,0,0);
// readucere pe inceput de fisier
pos=-1L;
// pozitie nume cautat
while (fread (&e,sizeof(e),1,f)==1)
if (strcmp (e.nume, nume)==0) {
pos= ftell(f)-sizeof(e);
break;
}
if ( pos < 0) break;
printf ("noua medie: "); scanf ("%f", &e.medie);
fseek (f,pos,0);
// pe inceput de articol gasit
fwrite(&e,sizeof(e),1,f);
//rescrie
articol
modificat
} while (eof != EOF);
fclose (f);
}

Problemele asociate realizrii unor aplicatii performante de gestiune a unor


fisiere de date mari (de obicei parte a unor baze de date integrate) sunt legate
de organizarea (structura) acestor fisiere astfel ca timpul de cutare dup
continut s fie ct mai mic si de actualizarea frecvent a datelor n conditii de
mentinere a integrittii si sigurantei datelor. Limbajul C asigur doar operatiile
primitive pentru scrierea programelor de gestiune si interogare a bazelor de
date.
Functiile de acces la fisiere disc se mai folosesc n diverse aplicatii si
programe utilitare care scriu si citesc fisiere grafice, fisiere audio, fisiere
comprimate si arhive de fisiere, biblioteci de functii, etc.

http://elth.srv.ro/

105

Descriptori de format n functii de I/E


Din familia functiilor de intrare-iesire se consider c fac parte si functiile
standard sscanf si sprintf, care au ca prim argument un sir de caractere ce
este analizat (scanat) de sscanf si respectiv produs de sprintf (litera s
provine de la cuvntul string). Aceste functii se folosesc fie pentru conversii
interne n memorie, dup citire sau nainte de scriere din/n fisiere text, fie
pentru extragere de subsiruri dintr-un sir cu delimitatori diferiti de spatii albe:
// extragere zi, luna si an dintr-un sir zz-ll-aaaa
void main () {
char d[]="25-12-1989"; int z,l,a;
sscanf (d,"%d-%d-%d",&z,&l,&a);
printf ("\n %d ,%d, %d \n",z,l,a);
}

Descriptorii de format sunt aceiasi ca si la functiile scanf, printf:


%c
= caractere individuale (cod ASCII)
%s
= sir de caractere ASCII
%p
= pointeri la void
%d, %i = numere ntregi cu semn n baza 10 (zecimale)
%u
= numere ntregi fr semn n baza 10
%x,%X
= numere ntregi fr semn n baza 16 (hexa)
%ld,%li = numere ntregi lungi
%f
= numere reale, cu parte ntreag si fractionar
%e,%E
= numere reale cu mantis si exponent (al lui 10)
%g
= numere reale n format %f sau %e, functie de
valoare
%lf,%le,%lg
= numere reale n precizie dubl (double)
%Lf,%Le,%Lg
= numere reale de tip long double

Intre caracterul % si literele care desemneaz tipul valorilor citite/scrise


mai pot apare, n ordine :
a) un caracter ce exprim anumite optiuni de scriere:
- (minus) aliniere la stnga n cmpul de lungime specificat
+ (plus) se afiseaz si semnul + pentru numere pozitive
0
numerele se completeaz la stnga cu zerouri pe lungimea w
#
form alternativ de scriere pentru numere
b) un numr ntreg w ce arat lungimea cmpului pe care se scrie o valoare,
sau caracterul * dac lungimea cmpului se d ntr-o variabil de tip int care
precede variabila a crei valoare se scrie.

http://elth.srv.ro/

106
c) punct urmat de un ntreg, care arat precizia (numr de cifre dup punctul
zecimal) cu care se scriu numerele nentregi.
d) una din literele h, l sau L care modific lungimea tipului numeric.
Exemplu de utilizare a optiunii 0 pentru a scrie 2 cifre si pentru 0..9 :
#include <stdio.h>
#include <time.h>
void main () { // afisare ora curenta (portabil)
struct tm t; time_t timer; // tipuri definite in time.h
timer =time(NULL);
// ora curent n milisecunde
t= *localtime (&timer); // conversie in ore,min,sec
printf("%02d:%02d:%02d\n",t.tm_hour,t.tm_min,t.tm_sec);
}

Exemplu de utilizare optiune - pentru aliniere siruri la stnga:


void main () {
char * s[] = {"unu","cinci","sapte","zece"};
int k, x[] ={1,5,7,10};
for (k=0;k<4;k++)
printf ("%-10s %4d \n", s[k],x[k]);
}

Exemplu de scriere cu format variabil (determinat la executie):


void main () {
long k=0, x[]= {1,11,111,1111,11111,111111};
int nc = (int)log10(x[5])+2;
// nr maxim de cifre + 2
for (k=0;k<5;k=k+2)
printf ("%*ld %*ld \n",nc, x[k],nc,x[k+1]);
}

11. Tehnici de programare n C


Stil de programare
Comparnd programele scrise de diversi autori n limbajul C se pot constata
diferente importante att n ceea ce priveste modul de redactare al textului
surs, ct si n utilizarea elementelor limbajului (instructiuni, declaratii,
functii, operatori, expresii, etc.).
O prim diferent de abordare este alegerea ntre a folosi ct mai mult
facilittile specifice oferite de limbajul C sau de a folosi constructii prezente si
n alte limbaje (Pascal de ex.). Exemple de constructii specifice limbajului C
de care se poate abuza sunt:

http://elth.srv.ro/

107
- Expresii complexe, incluznd prelucrri, atribuiri si comparatii.
- Utilizarea de operatori specifici: atribuiri combinate cu alte operatii, expresii
conditionale s.a.
- Utilizarea instructiunii "break".
- Utilizarea de pointeri n locul unor vectori sau matrice.
- Utilizarea unor declaratii complexe de tipuri, n loc de a defini tipuri
intermediare, mai simple. Exemplu:
// vector de pointeri la functii void f(int,int)
void (*tp[M])(int,int); // greu de citit !
// cu tip intermediar ptr pointer la functie
typedef void (*funPtr) (int,int);
funPtr tp[M];
// vector cu M comp. de tip funPtr

O alegere oarecum echivalent este ntre programe surs ct mai compacte


(cu ct mai putine instructiuni si declaratii) si programe ct mai explicite si
mai usor de nteles. In general este preferabil calitatea programelor de a fi
usor de citit si de modificat si mai putin lungimea codului surs si, eventual,
lungimea codului obiect generat de compilator. Deci programe ct mai clare si
nu programe ct mai scurte.
Exemplu de secvent pentru afisarea a n ntregi cte m pe o linie :
for ( i=1;i<=n;i++) {
printf ( "%5d%c",i, ( i%m==0 || i==n)? '\n':' ');

O variant mai explicit dar mai lung pentru secventa anterioar:


for ( i=1;i<=n;i++) {
printf ("%6d ",i);
if(i%m==0)
printf("\n");
}
printf("\n");

Conventii de scriere a programelor


Programele sunt destinate calculatorului si sunt analizate de ctre un
program compilator. Acest compilator ignor spatiile albe nesemnificative si
trecerea de la o linie la alta.
Programele sunt citite si de ctre oameni, fie pentru a fi modificate sau
extinse, fie pentru comunicarea unor noi algoritmi sub form de programe.
Pentru a fi mai usor de nteles de ctre oameni se recomand folosirea unor
conventii de trecere de pe o linie pe alta, de aliniere n cadrul fiecrei linii, de
utilizare a spatiilor albe si a comentariilor.

http://elth.srv.ro/

108
Respectarea unor conventii de scriere n majoritatea programelor poate
contribui la reducerea diversittii programelor scrise de diversi autori si deci la
facilitarea ntelegerii si modificrii lor de ctre alti programatori.
O serie de conventii au fost stabilite de autorii limbajului C si ai primului
manual de C. De exemplu, numele de variabile si de functii ncep cu o liter
mic si contin mai mult litere mici (litere mari numai n nume compuse din
mai multe cuvinte alturate, cum sunt nume de functii din MS-Windows).
Literele mari se folosesc n nume pentru constante simbolice. In ceea ce
priveste numele unor noi tipuri de date prerile sunt mprtite.
Una dintre conventii se refer la modul de scriere a acoladelor care
ncadreaz un bloc de instructiuni ce face parte dintr-o functie sau dintr-o
instructiune if, while, for etc. Cele dou stiluri care pot fi ntlnite n diferite
programe si crti sunt ilustrate de exemplele urmtoare:
void main ()
// Afisare numere perfecte , stil Linux
{
int n,m,s,d;
scanf (%d,&n);
for (m=2; m<=n; m++)
{
s=0;
for (d=1; d<m; d++)
{
if ( m % d ==0 )
s= s+ d;
}
if ( m==s)
printf (%6d\n,m);
}
}
// Afisare numere perfecte,
void main () {
int n,m,s,d;
scanf (%d,&n);
for (m=2; m<=n; m++){
s=0;
for (d=1; d<m; d++){
if ( m % d ==0 )
s= s+ d;
}
if ( m==s)
printf (%6d\n,m);
}
}

stil K&R si Java

http://elth.srv.ro/

109
Uneori se recomand utilizare de acolade chiar si pentru o singur
instructiune, anticipnd adugarea altor instructiuni n viitor la blocul
respectiv. Exemplu:
if ( m==s){
printf (%6d\n,m);
}

Pentru alinierea spre dreapta la fiecare bloc inclus ntr-o structur de control
se pot folosi caractere Tab (\t) sau spatii, dar evidentierea structurii de
blocuri incluse este important pentru oamenii acre citesc programe.
In cazul unor structuri de control multiple, suprapuse, se mai poate
simplifica programul prin definirea unor functii auxiliare, care includ o parte
din aceste structuri. De exemplu, n programul anterior se poate defini si folosi
o functie pentru calculul sumei divizorilor unui numr:
void main () {
int n,m;
scanf (%d,&n);
for (m=2; m<=n; m++){
if ( m==sumdiv(m))
printf (%6d\n,m);
}
}

O serie de recomandri se refer la modul cum trebuie documentate


programele folosind comentarii. Astfel fiecare functie C ar trebui precedat de
comentarii ce descriu rolul acelei functii, semnificatia argumentelor functiei,
rezultatul functiei pentru terminare normal si cu eroare, preconditii, plus alte
date despre autor, data ultimei modificri, alte functii utilizate sau
asemntoare, etc. Preconditiile sunt conditii care trebuie satisfcute de
parametri efectivi primiti de functie (limite, valori interzise, s.a) si care pot fi
verificate sau nu de functie. Exemplu:
// Functie de conversie numar ntreg pozitiv
// din binar n sir de caractere ASCII terminat cu zero
// value = numar intreg primit de functie (pozitiv)
// string = adresa unde se pune sirul rezultat
// radix = baza de numeratie (intre 2 si 16, inclusiv)
// are ca rezultat adresa sir sau NULL in caz de eroare
// trebuie completata pentru numere cu semn
char *itoa(int value, char *string, int radix) {
char digits[] = "0123456789ABCDEF";
char t[20], *tt=t, * s=string;
if ( radix > 16 || radix < 0 || value < 0) return NULL;
do {

http://elth.srv.ro/

110
*tt++ = digits[ value % radix];
} while ( (value = value / radix) != 0 );
while ( tt != t)
*string++= *(--tt);
*string=0;
return s;
}

Constructii idiomatice
Limbajul C poate fi derutant prin multitudinea posibilittilor de a exprima
un acelasi algoritm sau aceleasi prelucrri. Reducerea diversittii programelor
si a timpului de dezvoltare a programelor se poate face prin utilizarea unor
constructii idiomatice, consacrate de practica programrii n C.
Cuvintele "idiom", "idiomatic" se refer la particularittile unei limbi
(limbaj natural sau limbaj de programare), iar limbajul C exceleaz prin astfel
de particularitti.
Constructiile idiomatice n programare sunt denumite uneori sabloane sau
tipare ("patterns"), pentru c ele revin sub diverse forme n majoritatea
programelor, indiferent de autorii lor. Folosirea unor constructii idiomatice
permite programatorului s se concentreze mai mult asupra algoritmului
problemei si mai putin asupra mijloacelor de exprimare a acestui algoritm.
Specific limbajului C este utilizarea de expresii aritmetice sau de atribuire
drept conditii n instructiuni if, while, for, do n absenta unui tip logic
(boolean). Exemplu:
while (*d++ =*s++);

// copiaza sir de la s la d

In standardul C din 1999 s-a introdus un tip boolean, dar nu s-a modificat
sintaxa instructiunilor astfel c se pot folosi n continuare expresii aritmetice
drept conditii verificate. Limbajul Java a preluat toate instructiunile din C dar
cere ca intructiunile if, do,... s foloseasc expresii logice si nu aritmetice.
Pentru a facilita citirea programelor si trecerea de la C la Java este bine ca
toate conditiile s apar ca expresii de relatie si nu ca expresii aritmetice:
while (*s != 0)
*d++=*s++;

Un exemplu de constructie specific limbajului C este apelarea unei functii


urmat de verificarea rezultatului functiei, ntr-o aceeasi instructiune:
if ( f = fopen (fname,"r")) == NULL){
printf ("Eroare la deschidere fisier %s \n", fname);

http://elth.srv.ro/

111
exit(-1);
}

Utilizarea instructiunii for pentru cicluri cu numrare, cu o ultim expresie


de incrementare, este o constructie tipic limbajului C. Specific limbajului este
si numerotarea de la zero a elementelor unui vector (matrice). Exemplu:
for (i=0;i<n;i++)
printf (%g , x[i]);

Utilizarea de pointeri pentru prelucrarea sirurilor de caractere, cu


incrementare adresei din sir dup fiecare caracter prelucrat este un alt caz:
int strlen ( char * str){ // lungime sir terminat cu
zero
int len=0;
while ( *str++)
len++;
return len;
}

Un alt exemplu de sablon de programare este citirea unor nume dintr-un


fisier de date sau de la consol, alocarea dinamic de memorie pentru siruri si
memorarea adreselor ntr-un vector:
char buf[80], *a[1000]; int i=0;
while ( (scanf ("%s", buf) != EOF)) {
a[i]= (char*) malloc( strlen(buf)+1));
strcpy( a[i],buf); ++i;
}

sau, folosind o functie specific limbajului C:


while ( (scanf ("%s", buf) != EOF))
a[i]= strdup(buf);

Alocarea dinamic de memorie n C este o constructie idiomatic, care


foloseste operatorii sizeof si de conversie de tip. Conversia de tip pentru
variabile numerice si variabile pointer printr-un numr nelimitat de operatori
(un operator pentru fiecare tip) este de asemenea specific limbajului C.
In scrierea programelor cu interfat grafic sub Windows se folosesc multe
sabloane de cod, unele chiar generate automat de ctre mediul de dezvoltare.

Portabilitatea programelor

http://elth.srv.ro/

112
Un program C este portabil atunci cnd poate fi folosit (portat) pe orice
calculator si sub orice sistem de operare, fr modificarea textului surs.
Un program este portabil dac :
- nu foloseste extensii ale standardului limbajului C, specifice unei anumite
implementri a limbajului (unui anumit compilator) si nici elemente de C++.
- nu foloseste functii specifice unui sistem de operare sau unui mediu de
dezvoltare (functii nestandard).
- nu foloseste adrese de memorie sau alte particularitti ale calculatorului.
- nu foloseste particularitti ale mediului de dezvoltare (o anumit lungime
pentru numere ntregi sau pentru pointeri, anumite tipuri de biblioteci etc.).
In general pot fi portabile programele de aplicatii care folosesc numai functii
standard pentru intrri-iesiri (printf, scanf s.a.) si pentru alte servicii ale
sistemului de operare gazd (obtinere or curent, atribute fisiere etc.).
Programele care folosesc ecranul n mod grafic (cu ferestre, butoane,
diverse forme si dimensiuni de caractere etc.) sau care necesit pozitionarea pe
ecran n mod text sunt dependente de sistemul de operare gazd (Windows,
Linux etc.).
Pentru mrirea portabilittii programelor C standardul POSIX (Portable
Operating System) propune noi functii unice n C pentru acces la servicii care
ar trebui asigurate de orice sistem de operare compatibil POSIX.
Aflarea fisierelor dintr-un director si a atributelor acestora este un exemplu
de operatii care depind de sistemul gazd si nu se exprim prin functii
standard n C, desi sunt necesare n multe programe utilitare: listare nume
fisiere, arhivare fisiere, cutarea n mai multe fisiere a unui sir, s.a. Mai exact,
operatiile pot fi exprimate prin una sau dou functii, dar argumentele acestor
functii (structuri sau pointeri la structuri) depind de sistemul gazd.
Programul urmtor este utilizabil numai sub mediul Borland C :
#include <stdio.h>
#include <dir.h>
#include <string.h>
void main(int argc, char * argv[]) {
struct ffblk ffblk;
char mask[10]="*.*";
char *files[1000];
// vector de pointeri la nume
int done,n,i;
// creare vector cu nume fisiere gasite
n=0;
// numar de fisiere gasite
done = findfirst(mask,&ffblk,0xFF);
while (!done) {
files[n++]= strdup(ffblk.ff_name);
done = findnext(&ffblk);
}
for (i=0;i<n;i++) // afisare vector de nume
printf(" %s \n", files[i]);

http://elth.srv.ro/

113
}

Acelasi program n varianta mediului lcc-win32 (wedit) :


#include <stdio.h>
#include <io.h>
#include <string.h>
void main(int argc, char * argv[]) {
struct _finddata_t finfo; // necesara functiilor
char mask[10]="*.*"; char *files[1000] ;
long hndl; int n,i,err;
// creare vector cu nume fisiere gasite
n=0;
// numar de fisiere gasite
err= hndl = _findfirst(dirname,&finfo);
while (err >=0) {
files[n++]= strdup(finfo.name);
err = _findnext(hndl,&finfo); // <0 daca nu exista
}
// afisare vector de nume
...
}

Perechea de functii findfirst, findnext realizeaz enumerarea fisierelor


dintr-un director (al cror numr nu se cunoaste) si constituie elemente ale
unui mecanism iterator (enumerator) folosit si n alte situatii de programare.

Erori uzuale n programe C


Majoritatea erorilor de programare provin din faptul c ceea ce execut
calculatorul este diferit de intentiile programatorului. Erorile care se manifest
la executie au ca efect rezultate gresite si, mai rar, mesaje de eroare.
Descoperirea diferentelor dintre intentiile programatorului si actiunile
programului su se poate face prin depanarea programului. Depanarea se poate
face prin introducerea de instructiuni suplimentare n program n faza de
punere la punct (afisri de variabile, verificri cu assert s.a.) sau prin folosirea
unui program debugger care asist executia.
Exist cteva categorii de erori frecvente:
- Erori de algoritm sau de ntelegere gresit a problemei de rezolvat.
- Erori de exprimare a unui algoritm n limbajul de programare folosit.
- Erori de utilizare a functiilor standard sau specifice aplicatiei.
- Erori de preluare a datelor initiale (de citire date).
Utilizarea de variabile neinitializate este o surs de erori atunci cnd
compilatorul nu semnaleaz astfel de posibile erori ( nu se pot verifica toate

http://elth.srv.ro/

114
situatiile n care o variabil poate primi o valoare). In particular, utilizarea de
variabile pointer neinitializate ca adrese de siruri este o eroare uzual.
Indirectarea prin variabile pointer cu valoarea NULL sau neinitializate poate
produce erori de adresare care s afecteze si sistemul de operare gazd.
Erorile la depsirea memoriei alocate pentru vectori (indici prea mari sau
prea mici) nu sunt specifice limbajului C, dar nici nu pot fi detectate la
executie dect prin instructiuni de verificare scrise de programator ( n Pascal
si n Java aceste verificri la indici de vectori se fac automat).
Pentru programele cu intrri-iesiri n mod text, care folosesc functii de I/E
cu conversie de format (din familia scanf, printf) o greseal frecvent este
neconcordanta dintre sirul format (primul argument) si lista de variabile sau
expresii (urmtoarele argumente). Aceste erori se manifest numai prin
rezultatele (gresite) ale programelor. Exemple:
//

format de ntregi, valoare real


printf ( %d \n, 2.5); // scrie un numar gresit
// format pentru 3 numere, o singura valoare
printf (%d %d %d \n, 8); // scrie 3 numere
// variabile care nu sunt pointeri in scanf
scanf (%d, a );
// int a

O serie de greseli, care trec de compilare, se datoreaz necunoasterii


temeinice a limbajului sau neatentiei; n aceste cazuri limbajul trdeaz
intentiile programatorului.
Exemplul cel mai citat este utilizarea operatorului de atribuire pentru
comparatie la egalitate, probabil consecint a obisnuintelor din alte limbaje:
if ( a = b) printf ( a=b \n);

// if ( a==b ) ...

Alte erori sunt cauzate de absenta acoladelor pentru grupuri de instructiuni,


de absenta parantezelor n expresii pentru modificarea priorittii implicite de
calcul, de utilizarea gresit a tipurilor numerice si atribuirilor.
Operatiile cu siruri de caractere n C pot produce o serie de erori, mai ales c
exprimarea lor este diferit fat de alte limbaje: prin functii si nu prin
operatori ai limbajului. Functiile pe siruri nu pot face nici o verificare asupra
depsirii memoriei alocate pentru siruri deoarece nu primesc aceast
informatie, ci numai adresele sirurilor.
Definirea si utilizarea de functii
O functie nu trebuie s depseasc cam o pagin de text surs (cca 50 linii)
din mai multe motive: o functie nu trebuie s realizeze roluri ce pot fi

http://elth.srv.ro/

115
mprtite ntre mai multe functii, o functie nu trebuie s aib prea multe
argumente, o secvent prea lung de cod surs este mai greu de stpnit.
Programele reale totalizeaz sute si mii de linii surs, deci numrul de
functii din aceste programe va fi mare, iar functiile trebuie s comunice. Pe de
alt parte, transmiterea de rezultate prin argumente pointer n C nu este cea
mai simpl si nici cea mai sigur solutie pentru programatori.
Cea mai dificil situatie este a functiilor care lucreaz cu structuri de date
dinamice, definite prin unul sau mai multi pointeri. Functia primeste un
pointer (de exemplu, adresa de nceput a unei liste nlntuite) si poate
modifica acest pointer. Vom folosi ca exemple functii pentru operatii cu o
stiv list nlntuit de ntregi (list cu acces numai la primul element),
definit astfel:
typedef struct s {
int val;
struct s * leg;
} nod, * Stiva ;

La operatiile cu o stiv pot apare erori de tipul stiv goal (la extragere) si
stiv plin (la introducere), iar aceste situatii trebuie semnalate de functii.
Practica limbajului C este ca rezultatul ntreg al functiilor s indice modul de
terminare: zero cu succes si o valoare nenul pentru terminare anormal.
Functia de scoatere din stiv pop trebuie s transmit ca rezultat valoarea
scoas din vrful stivei, dar si modul de terminare.
Cea mai simpl solutie este utilizarea unei variabile externe, mai ales c cele
mai multe programe folosesc o singur stiv. Exemplu:
Stiva st;
// stiva ca variabila externa
void initSt (){
// initializare stiva
st = NULL;
}
int push (int x){ // pune in stiva un element
nod *p;
p = (nod*)malloc(sizeof(nod));
if(p==NULL)
return -1;
// eroare de alocare
p->val = x; p->leg = st; st = p;
return 1;
// operatie reusita
}
int pop (int * px){
// scoate din stiva un element
nod * p;
if (st==NULL) return -1;
// stiva goala
* px = st->val;
p = st->leg;
free (st) ; st = p;
return 0;
// operatie reusita

http://elth.srv.ro/

116
}
// program de test
void main () {
int x;
initSt ();
while (scanf("%d", &x ) > 0)
push (x);
printf ( " \n continut stiva : \n") ;
while (pop (&x) >= 0)
printf("%d \n", x );
}

Utilizarea de variabile externe este o mare tentatie n C pentru simplificarea


listelor de argumente, evitarea operatiilor cu pointeri si simplificarea definirii
functiilor. Totusi, nu se vor folosi variabile externe pentru transmiterea de date
ntre functii dect n cazuri rare, bine justificate. O functie care foloseste
variabile externe poate produce efecte secundare nedorite si este dependent
de contextul programului (de numele unor variabile exterioare functiei). O
astfel de functie nu poate fi reutilizat n alte programe si nu poate fi introdus
ntr-o bibliotec de functii.
O alt solutie fr variabile externe, sugerat de anumite functii standard din
C, este ca functiile s aib ca rezultat un pointer ce reprezint noua adres a
vrfului stivei. In caz de eroare acest rezultat va fi NULL, ca si n cazul unor
functii standard ca gets, malloc, strstr si altele.
// initializare stiva
Stiva initSt (void) {
return NULL;
}
// pune in stiva
Stiva push (Stiva sp, int x) {
Stiva p;
p = (Stiva) malloc (sizeof (nod));
if (p != NULL) {
p -> val =x;
p->leg = sp;
}
return p;
/* NULL daca alocare imposibila */
}
// daca stiva goala
int emptySt( Stiva sp) {
return sp==NULL;
}
// scoate din stiva
Stiva pop (Stiva sp, int * px) {
Stiva p;
if (sp == NULL)
return NULL;
/* stiva goala */
*px = sp-> val;

http://elth.srv.ro/

117
p =sp->leg;
free (sp);
return p;
}
void main () {
int x ; Stiva s;
s=initSt ();
while ( scanf ("%d",&x) > 0)
s=push (s,x);
while ( ! emptySt(s)) {
s=pop (s,&x);
printf ("%d \n",x);
}
}

De observat c secventa urmtoare nu este corect doarece dup ultima


extragere din stiv rezultatul functiei pop este NULL:
while ( s=pop (s,&x))
printf ("%d \n",x);

Modul de apelare al functiilor push si pop de mai sus este mai putin
obisnuit, iar apelarea acestor functii ca functii de tip void nu este semnalat ca
eroare la compilare si se manifest la executie prin rezultate incorecte.
O alt solutie posibil este transmiterea unui pointer la pointer ca argument al
functiilor, iar rezultatul s fie modul de terminare (ntreg):
void initSt ( Stiva * sp){ // initializare stiva
*sp = NULL;
}
int push (Stiva * sp,int x){ // pune in stiva un element
nod * p;
p = (nod*)malloc(sizeof(nod));
if (p==NULL) return -1;
// stiva plina
p->val = x; p->leg = *sp;
*sp = p;
return 0;
}
int pop (Stiva * sp,int * px){ // scoate din stiva
nod * p;
if (*sp==NULL) return -1;
// stiva goala
* px = (*sp)->val;
p = (*sp)->leg;
free (*sp) ;
*sp = p; return 0;
}
void main (){
// utilizare functii
int x; Stiva s ;
initSt (&s);
while (scanf("%d", &x ) > 0)

http://elth.srv.ro/

118
push (&s,x);
while ( pop (&s,&x) >=0)
printf("%d \n", x ) ;
}

O solutie poate fi si definirea unui tip structur care s contin variabila


pointer, cu transmiterea unui pointer la structur ca argument al functiilor:
typedef struct { nod * st } Stiva;
int push ( Stiva * sp, int x) {
nod *p; ...
sp->st->val=x; sp->st->leg = p;
...

// tipul Stiva

Ultima solutie examinat este si cea mai bun dar nu este proprie limbajului
C deoarece foloseste argumente de tip referint din C++. Unele implementri
de C admit si tipuri referint (exemplu lcc-win32).
void initS ( Stiva & sp){ // initializare stiva
sp = NULL;
}
int push (Stiva & sp, int x){ // pune in stiva un element
nod * p;
p = (nod*)malloc(sizeof(nod));
if (p==NULL) return -1;
// stiva goala
p->val = x; p->leg = sp;
sp = p;
return 0;
}
int pop (Stiva & sp, int & x){ // scoate din stiva
nod * p;
if (sp==NULL) return -1; // stiva goala
x = sp->val;
p = sp->leg;
free (sp) ;
sp = p; return 0;
}
// program de test
void main () {
int x; Stiva s ;
initS (s);
while (scanf("%d", &x ) > 0)
push (s,x);
while ( pop (s,x) >=0)
printf("%d \n", x ) ;
}

Avantajul principal este utilizarea simpl a functiilor, fr a folosi pointeri.

http://elth.srv.ro/

119
Exist riscul de a confunda operatorul de adresare & cu caracterul &
folosit n declararea argumentelor de tip referint si care nu este operator.
12. Dezvoltarea programelor mari n C
Particularitti ale programelor mari
Aplicatiile reale conduc la programe mari, cu mai multe sute si chiar mii de
linii surs. Un astfel de program sufer numeroase modificri (cel putin n faza
de punere la punct), pentru adaptarea la cerintele mereu modificate ale
beneficiarilor aplicatiei (pentru mbunttirea aspectului si modului de
utilizare sau pentru extinderea cu noi functii sau pentru corectarea unor erori
aprute n exploatare).
Programarea la scar mare este diferit de scrierea unor programe mici, de
scoal, si pune probleme specifice de utilizare a limbajului, a unor tehnici si
instrumente de dezvoltare a programelor, de comunicare ntre programatori si
chiar de organizare si coordonare a colectivelor de programatori.
Principala metod de stpnire a complexittii programelor mari este
mprtirea lor n module relativ mici, cu functii si interfete bine precizate.
Un program mare este format dintr-un numr oarecare de functii, numr de
ordinul zecilor sau sutelor de functii. Este bine ca aceste functii s fie grupate
n cteva fisiere surs, astfel ca modificri ale programului s se fac prin
editarea si recompilarea unui singur fisier surs (sau a cteva fisiere) si nu a
ntregului program (se evit recompilarea unor functii care nu au suferit
modificri). In plus, este posibil dezvoltarea si testarea n paralel a unor
functii din aplicatie de ctre persoane diferite.
Inainte de a ncepe scrierea de cod este necesar de obicei o etap care
contine de obicei urmtoarele:
- ntelegerea specificatiilor problemei de rezolvat si analiza unor produse
software asemntoare.
- stabilirea functiilor de bibliotec care pot fi folosite si verificarea modului
de utilizare a lor (pe exemple simple).
- determinarea structurii mari a programului: care sunt principalele functii din
componenta programului si care sunt eventualele variabile externe.
Pentru a ilustra o parte din problemele legate de proiectarea si scrierea
programelor mari vom folosi ca exemplu un program care s realizeze efectul
comenzii DIR din MS-DOS (dir si ls din Linux), deci s afiseze numele si
atributele fisierelor dintr-un director dat explicit sau implicit din directorul
curent. O parte din aceste probleme sunt comune mai multor programe
utilitare folosite n mod uzual.
Pentru nceput vom defini specificatiile programului, deci toate datele
initiale (nume de fisiere si optiuni de afisare), eventual dup analiza unor

http://elth.srv.ro/

120
programe existente, cu acelasi rol. Programul va fi folosit n mod linie de
comand si va prelua datele necesare din linia de comand.
O parte din optiunile de afisare au valori implicite; n mod normal se
afiseaz toate fisierele din directorul curent, nu se afiseaz fisierele din
subdirectoare si nu se afiseaz toate atributele fisierelor ci numai cele mai
importante. Exemple de utilizare:
dir
// toate fisierele din directorul curent, cu
atribute
dir c:\work
// toate fisierele din directorul work
dir *.c
// toate fisierele de tip c din directorul
curent
dir a:\pc lab*.txt // fisiere de tip txt din a:\pc
dir /B *.obj // fisiere de tip obj, fara atribute

Datele necesare programului sunt preluate din linia de comand si poate fi


necesar includerea ntre ghilimele a sirului ce descrie calea si tipul fisierelor:
dir c:\lcc\bin\*.*

Programul va contine cel putin trei module principale : preluare date


initiale (input), obtinere informatii despre fisierele cerute (getfiles) si
prezentarea acestor informatii (output), plus un program principal.
Aceste module pot fi realizate ca fisiere surs separate, pentru ca eventual
s se poat face trecerea spre o variant cu interfat grafic, cu izolarea
modificrilor necesare acestei treceri si evitarea editrii unui singur fisier surs
foarte mare (dac tot programul se realizeaz ca un singur fisier).

Compilri separate si fisiere proiect


Pe lng aspectele ce tin de limbajul folosit, dezvoltarea si ntretinerea
programelor mari ridic si probleme practice, de operare, ce depind de
instrumentele software folosite (compilator mod linie de comand sau mediu
integrat IDE) si de sistemul de operare gazd.
In urma compilrii separate a unor fisiere surs rezult tot attea fisiere
obiect (de tip OBJ), care trebuie s fie legate mpreun ntr-un singur program
executabil. In plus, este posibil ca aplicatia s foloseasc biblioteci de functii
nestandard, create de alti utilizatori sau create ca parte a aplicatiei.
Bibliotecile de functii sunt de dou categorii distincte:
- Biblioteci cu legare static, din care functiile sunt extrase n faza editrii de
legturi si sunt atasate programului executabil creat de linkeditor. Diferenta
dintre o bibliotec static si un modul obiect este aceea ca un fisier obiect

http://elth.srv.ro/

121
(OBJ) este atasat integral aplicatiei, dar din bibliotec se extrag si se adaug
aplicatiei numai functiile (modulele obiect) apelate de aplicatie.
- Biblioteci cu legare dinamic (numite DLL n sistemul Windows), din care
functiile sunt extrase n faza de executie a programului, ca urmare a apelrii
lor. Astfel de biblioteci, folosite n comun de mai multe aplicatii, nu mresc
lungimea programelor de aplicatie, dar trebuie furnizate mpreun cu aplicatia.
Un alt avantaj este acela c o bibliotec dinamic poate fi actualizat (pentru
efectuarea de corecturi sau din motive de eficient) fr a repeta construirea
aplicatiei care o foloseste (editarea de legturi). In MS-DOS nu se pot folosi
biblioteci cu legare dinamic.
In legtur cu compilarea separat a unor prti din programele mari apar
dou probleme:
- Enumerarea modulelor obiect si bibliotecilor statice componente.
- Descrierea dependentelor dintre diverse fisiere (surse, obiect, executabile)
astfel ca la modificarea unui fisier s se realizeze automat comenzile necesare
pentru actualizarea tuturor fisierelor dependente de cel modificat. Ideea este
de gestiune automat a operatiilor necesare ntretinerii unui program mare, din
care se modific numai anumite prti.
Pentru dezvoltarea de programe C n mod linie de comand solutiile celor
dou probleme sunt:
- Enumerarea fisierelor obiect si bibliotecilor n comanda de linkeditare.
- Utilizarea unui program de tip make si a unor fisiere ce descriu dependente
ntre fisiere si comenzi asociate (makefile).
Atunci cnd se foloseste un mediu integrat pentru dezvoltare (IDE) solutia
comun celor dou probleme o constituie fisierele proiect. Desi au cam
aceleasi functii si suport cam aceleasi operatii, fisierele proiect nu au fost
unificate si au forme diferite pentru medii IDE de la firme diferite sau din
versiuni diferite ale unui IDE de la o aceeasi firm (de ex. Borland C ).
In forma sa cea mai simpl un fisier proiect contine cte o linie pentru
fiecare fisier surs sau obiect sau bibliotec ce trebuie folosit n producerea
unei aplicatii. Exemplu de fisier proiect din Borland C :
input.c

getfiles.c

output.c

dirlist.c

Operatiile principale cu un fisier proiect sunt: crearea unui nou proiect,


adugarea sau stergerea unui fisier la un proiect si executia unui fisier proiect.
Efectul executiei unui fisier proiect depinde de continutul su dar si de data
ultimei modificri a unui fisier din componenta proiectului. Altfel spus, pot
exista dependente implicite ntre fisierele dintr-un proiect:

http://elth.srv.ro/

122
- Dac data unui fisier obiect (OBJ) este ulterioar datei unui fisier executabil,
atunci se reface automat operatia de linkeditare, pentru crearea unui nou fisier
executabil.
- Dac data unui fisier surs (C sau CPP) este ulterioar datei unui fisier
obiect, atunci se recompileaz fisierul surs ntr-un nou fisier obiect, ceea ce
va antrena si o nou linkeditare pentru actualizarea programului executabil.
Fisiere antet
Functiile unei aplicatii pot folosi n comun urmtoarele elemente de limbaj:
- tipuri de date definite de utilizatori
- constante simbolice
- variabile externe
Tipurile de date comune se definesc de obicei n fisiere antet (de tip H), care
se includ n compilarea fisierelor surs cu functii (de tip C sau CPP). Tot n
aceste fisiere se definesc constantele simbolice si se declar functiile folosite
n mai multe fisiere din componenta aplicatiei.
Exemplu de fragment dintr-un fisier antet folosit n programul dirlist:
struct file {
char fname[13];
// nume fisier (8+3+.+0)
long fsize;
// dimensiune fisier
char ftime[26] ; // data ultimei modificari
short isdir;
// daca fisier director
};
#define MAXC 256
// dimensiunea unor siruri
#define MAXF 1000 // numar de fisiere estimat

Fisierul antet dirlist.h poate include fisiere antet standard comune


(stdio.h, stdlib.h ), dar este posibil ca includerile de fisiere antet standard
s fac parte din fiecare fisier surs al aplicatiei.
In general, comunicarea dintre functii se va realiza prin argumente si prin
rezultatul asociat numelui functiei si nu prin variabile externe (globale). Exist
totusi situatii n care definirea unor variabile externe, folosite de un numr
mare de functii, reduce numrul de argumente, simplific utilizarea functiilor
si produce un cod mai eficient.
In programul dirlist astfel de variabile comune mai multor functii pot fi:
calea ctre directorul indicat, masca de selectie fisiere si lista de optiuni de
afisare. Functia getargs din fisierul input.c preia aceste date din linia de
comand, dar ele sunt folosite de functii din celelalte dou fisiere getfiles.c
si output.c. Variabilele externe se definesc ntr-unul din fisierele surs ale
aplicatiei, de exemplu n dirlist.c care contine functia main:
char path[MAXC], mask[MAXC], opt[MAXC];

// var comune

http://elth.srv.ro/

123
Domeniul implicit al unei variabile externe este fisierul n care variabila este
definit (mai precis, functiile care urmeaz definitiei). Pentru ca functii din
fisiere surs diferite s se poat referi la o aceeasi variabil, definit ntr-un
singur fisier este necesar declararea variabilei respective cu atributul extern,
n toate fisierele unde se fac referiri la ea. Exemplu :
extern char path[MAXC], mask[MAXC], opt[MAXC];

Directive preprocesor utile n programele mari


Directivele preprocesor C au o sintax si o prelucrare distinct de
instructiunile si declaratiile limbajului, dar sunt parte a standardului limbajului
C. Directivele sunt interpretate ntr-o etap preliminar compilrii (traducerii)
textului C, de un preprocesor.
O directiv ncepe prin caracterul # si se termin la sfrsitul liniei curente
(daca nu exist linii de continuare a liniei curente). Nu se foloseste caracterul
; pentru terminarea unei directive.
Cele mai importante directive preprocesor sunt :
// inlocuieste toate aparitiile identificatorului ident prin sirul text
#define ident

text

// defineste o macroinstructiune cu argumente


#define ident (a1,a2,...) text

// include in compilare continutul fisierului sursa fisier


#include fisier

// compilare conditionata de valoarea expresiei expr


#if

expr

// compilare conditionata de definirea unui identificator (cu #define)


#if defined

ident

// terminarea unui bloc introdus prin directiva #if


#endif

Directiva define are multiple utilizari n programele C :


a) - Definirea de constante simbolice de diferite tipuri (numerice, text)
b) - Definirea de macrouri cu aspect de functie, pentru compilarea mai
eficient a unor functii mici, apelate n mod repetat. Exemple:
# define max(A,B) ( (A)>(B) ? (A):(B) )
#define
random(num)(int)(((long)rand()*(num))/(RAND_MAX+1))
#define randomize()
srand((unsigned)time(NULL))

Macrourile pot contine si declaratii, se pot extinde pe mai multe linii si pot
fi utile n reducerea lungimii programelor surs si a efortului de programare.

http://elth.srv.ro/

124
In standardul din 1999 al limbajului C s-a preluat din C++ cuvntul cheie
inline pentru declararea functiilor care vor fi compilate ca macroinstructiuni n
loc de a folosi macrouri definite cu define.
c)- Definirea unor identificatori specifici fiecrui fisier si care vor fi testati cu
directiva ifdef. De exemplu, pentru a evita declaratiile extern n toate fisierele
surs, mai putin fisierul ce contine definitiile variabilelor externe, putem
proceda astfel:
- Se defineste n fisierul surs cu definitiile variabilelor externe un nume
simbolic oarecare:
// fisierul DIRLIST.C
#define MAIN

- In fisierul dirlist.h se plaseaz toate declaratiile de variabile externe, dar


ncadrate de directivele if si endif:
// fisierul DIRLIST.H
#if !defined(MAIN)
// sau ifndef MAIN
extern char path[MAXC], mask[MAXC], opt[MAXC];
#endif

Directiva include este urmat de obicei de numele unui fisier antet (de tip H
= header), fisier care grupeaz declaratii de tipuri, de constante, de functii si
de variabile, necesare n mai multe fisiere surs (C sau CPP). Fisierele antet nu
ar trebui s contin definitii de variabile sau de functii, pentru c pot apare
erori la includerea multipl a unui fisier antet. Un fisier antet poate include
alte fisiere antet.
Pentru a evita includerea multipl a unui fisier antet (standard sau
nestandard) se recomand ca fiecare fisier antet s nceap cu o secvent de
felul urmtor:
#ifndef HDR
#define HDR
// continut fisier HDR.H ...
#endif

Fisierele antet standard (stdio.h s.a.) respect aceast recomandare.


O solutie alternativ este ca n fisierul ce face includerea s avem o secvent
de forma urmtoare:
#ifndef
STDIO_H
#include
<stdio.h>
#define
_STDIO_H
#endif

http://elth.srv.ro/

125
Directivele de compilare conditionat de forma if...endif au si ele mai multe
utilizri ce pot fi rezumate la adaptarea codului surs la diferite conditii
specifice, cum ar fi:
- dependenta de modelul de memorie folosit ( n sistemul MS-DOS)
- dependenta de sistemul de operare sub care se foloseste programul (de ex.,
anumite functii sau structuri de date care au forme diferite n sisteme diferite)
- dependenta de fisierul surs n care se afl (de exemplu tcalc.h).
Directivele din grupul if au mai multe forme, iar un bloc if ... endif poate
contine si o directiva elseif.

Proiectul initial
Majoritatea produselor software se preteaz la dezvoltarea lor treptat,
pornind de la o versiune minimal initial, extins treptat cu noi functii. Prima
form, numit si prototip, trebuie s includ partea de interfat cu utilizatorul
final, pentru a putea fi prezentat repede beneficiarilor, care s-si precizeze ct
mai devreme cerintele privind interfata cu operatorii aplicatiei.
Dezvoltarea n etape nseamn ns si definirea progresiv a functiilor din
componenta aplicatiei, fie de sus n jos (top-down), fie de jos n sus
(bottom-up), fie combinat. Abordarea de sus n jos stabileste functiile
importante si programul principal care apeleaz aceste functii. Dup aceea se
defineste fiecare functie, folosind eventual alte functii nc nedefinite, dar care
vor fi scrise ulterior. In varianta initial programul principal arat astfel :
void main(int argc, char * argv[]) {
char *files[MAXF];
// vector cu nume de fisiere
int nf;
// numar de fisiere
getargs (argc,argv);
// preluare date
nf=listFiles(files);
// creare vector de fisiere
printFiles(files,nf); // afisare cu atribute
}

Abordarea de jos n sus porneste cu definirea unor functii mici, care vor fi
apoi apelate n alte functii, s.a.m.d. pn se ajunge la programul principal.
Pentru aflarea fisierelor de un anumit tip dintr-un director dat se pot folosi
functiile nestandard findffirst si findnext, care depind de implementare.
Pentru determinarea atributelor unui fisier cu nume dat se poate folosi functia
stat (file status) sau fstat, declarate n fisierul antet <sys/stat.h> mpreun
cu tipul structur folosit de functie (struct stat). Structura contine
dimensiunea fisierului (st_size), data de creare (st_ctime), data ultimei
modificri si doi octeti cu atributele fisierului (st_mode): fisier normal sau
director, dac poate fi scris (sters) sau nu etc. Anumite atribute depind de

http://elth.srv.ro/

126
sistemul de operare gazd si pot lipsi n alte sisteme, dar functia stat si
structura stat sunt aceleasi pentru diverse implementri. Pentru determinarea
atributelor, fisierul trebuie mai nti deschis. Prototip stat :
int stat (char * filename, struct stat * statptr);

cu rezultat 0 dac fisierul specificat n filename este gsit si 1 dac negsit.


Functia stat poate s primeasc numele complet, cu cale, al fisierului aflat
ntr-un alt director dect programul care se execut.
Pentru extragerea unor biti din cmpul st_mode sunt prevzute constante
simbolice cu nume sugestive. Exemplu:
// verific dac file este fisier normal sau director
err=stat (file, &finfo);
// pune atribute in finfo
if (finfo.st_mode & S_IFDIR)
printf ("Directory \n" );
else
printf ("Regular file \n" );

Functia stat si structura stat se pot folosi la fel n mai multe


implementri, desi nu sunt standardizate in ANSI C.
Pentru conversia datei si orei de creare a unui fisier (un numr long) n
caractere se foloseste una din functiile standard ctime sau asctime.
Utilizarea acestor functii necesit includerea unor fisiere antet:
#include
#include
#include
#include
#include

<io.h>
//
<direct.h>
//
<sys/stat.h> // stat
<time.h>
// ctime
<string.h>

Primul modul din programul nostru va fi modulul de preluare a datelor


initiale: nume fisier director al crui continut se afiseaz (cu calea la director),
nume/tip fisiere listate si optiuni de afisare. Aici se fac si verificri asupra
utilizrii corecte a programului si alte operatii de pregtire a datelor pentru
modulele urmtoare. Vom porni cu o variant n care nu se admit optiuni si se
afiseaz numai fisiere din directorul curent, specificate printr-o masc ce poate
contine caractere * si/sau ?. Deci comanda de lansare a programului poate
contine un singur argument (un sir masc) sau nici unul; dac nu se d nici un
argument se consider masca *.*, deci se afiseaz toate fisierele.
Varianta initial pentru primul modul poate fi urmtoarea:
// preluare argumente din linia de comanda
void getargs (int argc,char *argv[]) {
char *p;
if (argc < 2){
// daca nu exista argument

http://elth.srv.ro/

127
strcpy(mask,"*.*"); return;
}
p = strrchr(argv[1],'\\'); // ultimul caracter \
if (p==0)
strcpy(mask,argv[1]);
else {
printf("Numai
fisiere
din
acest
director \n");
exit(2);
}
}

Urmtorul modul, si cel mai important, este cel care obtine din sistem
informatiile necesare pentru afisare: lista de fisiere si atributele fiecrui fisier.
Varianta urmtoare este pentru mediul Borland C:
int listFiles ( char* files[]) {
struct ffblk finfo;
int n, err; char full[256];
n=0;
// numar de fisiere gasite
strcpy(full,path); strcat(full,mask);
err= findfirst(full,&finfo,0xff);
while (err >=0 ) {
files[n++]= strdup(finfo.ff_name);
err = findnext(&finfo);
}
return n;
}

Ultimul modul este cel care se ocup de prezentarea listei de fisiere n


functie de optiunile explicite sau implicite. In varianta initial se afiseaz
numele, lungimea si data de creare a fiecrui fisier, cu exceptia fisierelor
director pentru care nu se poate obtine simplu dimensiunea total. La sfrsitul
listei se afiseaz numrul total de fisiere si dimensiunea lor total.
// afisare lista fisiere
void printFiles ( char * f[], int nf) {
long size, tsize=0L;
// dimensiune totala fisiere
int i; FILE* fp; short isdir;
struct stat fst;
char tim[26], full[256];
printf ("\n\n");
// listare completa, cu dimensiune totala
for (i=0;i<nf;i++) {
strcpy(full,path); strcat(full,f[i]);
fp=fopen(full,"r");
stat (full, &fst);
size= fst.st_size;
// dimensiune fisier
tsize += size;
isdir = fst.st_mode & S_IFDIR;
strcpy(tim,ctime(&fst.st_ctime));
tim[strlen(tim)1]=0;

http://elth.srv.ro/

128
if ( isdir)
printf("%-12s <DIR>\t\t%s \n", f[i],tim );
else
printf("%-12s %8ld %s \n", f[i],size,tim);
}
printf ("\t%d Files \t %ld bytes \n", nf, tsize);
}

Formatul de afisare este apropiat de cel al comenzii DIR din MS-DOS dar
nu identic, din cauza folosirii functiei ctime si a altor simplificri.

Extinderea programului
Programul nostru poate fi extins treptat, prin adugarea de noi optiuni de
afisare, fr modificri esentiale n versiunile precedente ale programului.
Preluarea optiunilor din linia de comand poate fi relativ simpl dac vom
considera c fiecare optiune este un sir separat, care ncepe cu / (de obicei se
admite gruparea mai multor optiuni ntr-un sir precedat de /). Optiunile pot fi
scrise n orice ordine, nainte si/sau dup numele directorului si masc:
dirlist /B c:\games\*.* /OS

Optiunile comenzii DIR pot avea una sau dou litere, dar numrul de litere
nu conteaz dac fiecare optiune se termin cu spatiu alb.
Rezultatul prelucrrii optiunilor din linia de comand va fi un sir n care
literele ce denumesc fiecare optiune sunt separate ntre ele printr-un caracter /.
void getargs (int argc, char *argv[] ) {
char *p; char f[80];
int i;
opt[0]=0;
if (argc <2){
strcpy(mask,"*.*"); strcpy(path,".\\");
return;
}
for (i=0;i<argc;i++){
strcpy(f,argv[i]);
// numai ptr simplificare cod
if (f[0]=='/') {
// daca optiune
strcat(opt,f); continue;
}
// argument care nu e optiune
p = strrchr(f,'\\');
if (p) {
// daca contine nume director
strncpy(path,f, p-f+1);path[p-f+1]=0;
strcpy(mask,p+1);
}

http://elth.srv.ro/

129
else {
// daca nu contine nume director
strcpy(mask,f); strcpy(path,".\\");
}
}
}

Verificarea existentei unei optiuni se reduce la cutarea sirului ce codific


optiunea n sirul opt care reuneste toate optiunile. Exemplu:
if (strstr (opt,/b)||strstr(opt,/B)) ...

Interpretarea unei optiuni poate fi mai simpl sau mai complicat, functie de
tipul optiunii. Optiunea /B (brief) este cea mai usor de tratat si o vom da ca
exemplu. In ciclul principal din functia printFiles se va insera secventa
urmtoare:
if (strstr(opt,"b")){
// nu se afiseaza numele . si ..
if (strcmp(f[i],".")&& strcmp(f[i],".."))
printf("%-12s \n", f[i]); // doar numele
continue;
// urmatorul fisier din lista
}

Pentru ordonarea listei de fisiere dup un atribut (nume, extensie, mrime,


dat) este necesar memorarea acestor atribute pentru toate fisierele. In acest
scop este util definirea unei structuri mai mici ca structura stat care s
reuneasc numai atributele necesare la ordonare:
struct file {
char fname[13];
// nume fisier redus la primele 8
car.
long fsize;
// dimensiune fisier
char ftime[26] ; // data ultimei modificari
char isdir;
// daca fisier director sau ordinar
};

Vom scrie o functie care s determine atributele fisierelor si s le memoreze


ntr-un vector de structuri de tip struct file:
// creare vector cu atribute fisiere
void fileAttr (char * files[], int nf, struct file fat[])
{
struct stat fstat;
FILE * fp;
int i; char * p, *f, full[MAXC];
for (i=0;i<nf;i++) {
f=files[i]; // ptr simplificarea expresiilor

http://elth.srv.ro/

130
strcpy(full,path); strcat(full,f);
fp=fopen(full,"r");
stat (full, &fstat);
fat[i].isdir = fstat.st_mode & S_IFDIR;
strcpy(fat[i].ftime, ctime (&fstat.st_ctime));
fat[i].ftime[strlen(fat[i].ftime)-1]=0;
if ( strcmp(f,".")==0 || strcmp(f,"..")==0) {
strcpy(fat[i].fname,f);
continue;
}
fat[i].fsize = fstat.st_size; // dimensiune fisier
strcpy (fat[i].fname, f);
// nume fisier
}
}

Functia de afisare printFiles va primi acum vectorul de structuri file si


dimensiunea sa si va suferi unele modificri.
Vectorul de structuri va fi alocat n functia main, cu dimensiune fix sau
dinamic, deoarece se cunoaste acum numrul exact de fisiere din director.
Modificrile din functia main pentru apelul functiilor vor fi minore.
Ordonarea vectorului de structuri dup orice cmp al structurilor este simpl
dac se foloseste functia de bibliotec qsort. Pentru fiecare criteriu de
sortare este necesar o functie de comparare (cu prototip impus). Ca exemplu
urmeaz dou astfel de functii si utilizarea lor n qsort:
// comparare dupa nume
int cmpext(const void* a, const void * b) {
struct file * af =(struct file*)a;
struct file * bf =(struct file*)b;
return strcmp(af->fname,bf->fname);
}
// comparare dupa lungime
int cmpsize(const void* a, const void * b) {
struct file * af =(struct file*)a;
struct file * bf =(struct file*)b;
return (int)(af->fsize - bf->fsize);
}
// ordonare lista fisiere dupa lungime
void sortBySize (struct file f[], int nf) {
qsort ( f, nf, sizeof(struct file), cmpsize);
}

Pentru ordonare dup tipul fisierelor trebuie separat extensia de nume.


Cel mai dificil de realizat este optiunea de afisarea recursiv a fisierelor din
subdirectoarele directorului dat, deoarece necesit eliminarea variabilei
externe path si introducerea ei ca argument n functia recursiv printFiles
si n celelalte functii care o folosesc : getargs si listFiles.

http://elth.srv.ro/

131

Imbunttirea programului
Un program corect si complet poate fi perfectionat pentru:
- Reducerea posibilittilor de terminare anormal, fr mesaje explicite.
- Reducerea timpului de executie si a memoriei ocupate.
- Imbunttirea modului de prezentare a rezultatelor.
- Facilitarea unor extinderi sau modificri ulterioare
- Facilitarea reutilizrii unor prti din program n alte aplicatii.
In versiunea final a programului trebuie prevzute toate situatiile n care ar
putea apare erori si mesaje corespunztoare. Nu am verificat dac programul
primeste optiuni care nu au sens pentru el, nu am verificat existenta fisierelor
la deschidere cu fopen sau la apelarea functiei stat. In general, fiecare apel
de functie trebuie urmat imediat de verificarea rezultatului ei. Exemplu:
if ( (fp=fopen(full,"r")) ==NULL){
printf( Eroare la fopen: fisier %s,full);
exit(-1);
}
if (stat (full, &fstat)!= 0)
printf ( Eroare la functia stat: fisier %s,full);
exit (-1);
}

Vectorul de pointeri la nume de fisiere are o dimensiune fix MAXF, aleas


arbitrar si care ar putea s fie insuficient uneori. O solutie mai bun este o
alocare dinamic initial de memorie si modificarea functiei listFiles pentru
extindere automat prin realocare dinamic:
char

**files= (char**) malloc(MAXFILES*sizeof(char*));

Numrul total de fisiere din directorul curent si din subdirectoare sale poate
fi foarte mare, iar programul trebuie s fac fat oricrui numr.
In program exist si alte limite (la siruri de caractere) iar ncadrarea n aceste
limite trebuie verificat sau se recurge la alocare si realocare dinamic pentru
eliminarea unor limitri arbitrare.
Comparnd cu modul de afisare realizat de comanda DIR programul nostru
necesit mai multe modificri:
- Numrul de octeti ocupat de un fisier si de toate fisierele poate avea multe
cifre iar pentru a fi mai usor de citit trebuie separate grupe de cte 3 cifre prin
virgule. Exemplu: 12,345,678 bytes.
Functia urmtoare transform un numr lung ntr-un astfel de sir:
void format(long x, char * sx) {

http://elth.srv.ro/

132
int r[10],i=0; char aux[4];
*sx=0;
// pregatire strcat(sx,...)
while ( x > 0) {
r[++i]=x%1000; // un numar de max 3 cifre
x=x/1000;
}
while ( i >0){
printf("%d\n",r[i]);
sprintf(aux,"%d",r[i--]);
strcat(sx,aux); strcat(sx,",");
}
sx[strlen(sx)-1]=0; // elimina ultima virgula
}

- Sirul furnizat de functia ctime este greu de citit si contine date inutile (ex.
numele zilei din sptmn), deci mai trebuie prelucrat ntr-o functie.
- In sistemul MS-Windows numele de fisiere nu sunt limitate la 8+3 ca n
MS-DOS si deci va trebui prelucrat pentru reducere la 12 caractere. Programul
NC (Norton Commander) nu retine primele 8 caractere din nume (care pot fi
identice pentru mai multe nume) si formeaz un nume din primele 6 caractere
ale numelui complet, caracterul ~ si o cifr (1,2,3...). Comanda DIR afiseaz
si acest nume prescurtat si numele complet (sau o parte din el).
Functiile findfirst si findnext specifice sistemului MS-DOS fac automat
aceast reducere a numelui, dar alte functii nu o fac si trebuie realizat n
programul de listare.
O parte din functiile programului dirlist pot fi reutilizate si n alte
programe: preluare optiuni si nume fisiere din linia de comand, afisare
numere ntregi foarte mari s.a.

Concluzii
Un program complet pentru comanda DIR este mult mai mare dect schita
de program prezentat anterior, dar este mult mai mic si mai simplu dect alte
programe necesare n practic.
Problemele ridicate de acest program sunt oarecum tipice pentru multe alte
programe reale si permite urmtoarele concluzii:
- Necesitatea stpnirii tuturor aspectelor limbajului folosit : operatii cu siruri
de caractere, cu structuri si vectori de structuri, cu fisiere, alocare dinamic,
transmiterea de date ntre functii, scrierea de functii recursive etc.
- Necesitatea cunoasterii, cel putin la nivel de inventar, a functiilor
disponibile n biblioteci si exersarea lor separat, nainte de a fi folosite ntr-un
program mare.

http://elth.srv.ro/

133
- Dezvoltarea progresiv a programelor, cu teste ct mai complete n fiecare
etap. Este bine s pstrm mereu versiunile corecte anterioare, chiar
incomplete, pentru a putea reveni la ele dac prin extindere se introduc erori
sau se dovedeste c solutia de extindere nu a fost cea mai bun.
- Activitatea de programare necesit mult atentie si concentrare precum si
stpnirea detaliilor, mai ales ntr-un limbaj cum este C. La orice pas trebuie
avute n vedere toate posibilittile existente si tratate.
- Comentarea rolului unor variabile sau instructiuni se va face chiar la
scrierea lor n program si nu ulterior. Numrul acestor comentarii va fi mult
mai mare dect cel din exemplul prezentat, mai ales la fiecare antet de functie.
Aceste comentarii pot facilita adaptarea programului pentru un alt sistem de
operare sau pentru o alt interfat cu utilizatorii programului.
13. Programare generic n C
Structuri de date si algoritmi
Interesul pentru studiul structurilor de date (colectiilor) este determinat de
faptul c la elaborarea unui nou program (la proiectare) se pune att problema
alegerii algoritmilor celor mai performanti ct si problema alegerii structurilor
de date celor mai adecvate. Acest adevr este exprimat si n titlul crtii lui
Niclaus Wirth : Algorithms + Data Structures = Programs.
Evolutia disciplinei Structuri de date si algoritmis-a produs n cteva etape
importante:
- Colectarea si inventarierea structurilor de date folosite n diverse aplicatii.
- Sistematizarea structurilor de date si desprinderea de limbaje si de aplicatii
concrete, prin abstractizare si generalizare.
- Furnizarea de functii (clase) generale, direct utilizabile n aplicatii.
Structura unei colectii si modul de legare a elementelor sunt importante
pentru c determin algoritmii asociati colectiei (modul de realizare a
operatiilor cu o colectie). De exemplu, un algoritm de cutare a unei valori
(sau a unei perechi cu cheie dat) nu depinde de tipul datelor memorate ci de
tipul colectiei: vector, list nlntuit, arbore binar, tabel de dispersie, etc.
Functia de cutare, scris n C, depinde ns si de tipul datelor, care determin
operatia de comparare a datelor.
Procesul de generalizare a structurilor de date si operatiilor asociate a
evoluat n dou directii:
- Colectii de date generice, care pot contine date de orice tip predefinit sau
definit de utilizatori.
- Structuri (tipuri) abstracte de date, care au aceeasi utilizare dar implementri
diferite.
Un exemplu este aplicatia n care se determin frecventa de aparitie a

http://elth.srv.ro/

134
cuvintelor distincte ntr-un text. Crtile mai vechi prezint problema ca o
aplicatie pentru arbori binari de cutare, deoarece sunt necesare cutri
frecvente n lista de cuvinte si mentinerea ei n ordine. Perspectiva modern
asupra acestei probleme este aceea c este necesar un tip abstract de date
numit dictionar (sau asociere), care este o colectie de perechi cheie-valoare
(aici cheia este cuvntul, iar valoarea este numrul de aparitii). Tipul abstract
dictionar (Map) poate fi implementat printr-un tabel de dispersie sau printrun arbore echilibrat de cutare, sau prin alte structuri de date dintre care unele
sunt disponibile sub form de clase predefinite.
Alte tipuri abstracte sunt: multimi, liste generale, stive, cozi, s.a

Colectii de date
O colectie de date (numit si structur de date) grupeaz mai multe
componente, numite si elemente ale colectiei. Componentele unei colectii sunt
fie valori individuale (numere, siruri de caractere, sau alte tipuri de date), fie
perechi cheie-valoare, fie alte colectii sau referinte (pointeri) la date sau la
colectii.
Clasificarea colectiilor de date se face de obicei dup relatiile existente ntre
elemente (dup structura intern a colectiei) si dup operatiile specifice
colectiei, dar nu si dup tipul datelor componente. Astfel avem colectii liniare
(vectori, liste liniare, stive, cozi, tabele de dispersie) si colectii neliniare
(arbori binari, arbori oarecare, grafuri ).
In esent, structurile de date fizice (fundamentale) sunt de dou tipuri mari:
- Structuri de date memorate la adrese consecutive ( Vectori)
- Structuri de date dispersate n memorie, dar legate prin pointeri.
De multe ori se foloseste expresia structuri de date dinamice pentru
colectiile de variabile alocate dinamic si legate prin pointeri : liste cu legturi
si arbori (cu pointeri). De fapt, si un vector alocat dinamic este tot o structur
dinamic, n sensul c alocarea memoriei se face la executie.
Din punct de vedere practic, al programrii, este important si tipul datelor
memorate n fiecare element dintr-un vector sau dintr-o list, sau dintr-un
arbore. Astfel putem avea un vector de numere ntregi, sau un vector de
structuri, sau un vector de pointeri la siruri de caractere sau la tipuri structur.
In principiu exist dou posibilitati pentru implementarea operatiilor cu
colectii de date:
- Utilizatorii s-si scrie singuri operatiile cu liste, arbori, etc. pentru tipurile
de date specifice aplicatiei sale, n general prin adaptarea unor subprograme
existente, publicate n literatura de specialitate sau preluate din alte programe.
- Utilizatorii s foloseasc biblioteci de functii generale pentru operatii cu

http://elth.srv.ro/

135
colectii ce pot contine date de orice tip, cu precizarea tipului la apelarea
functiilor.

Colectii de date generice


O multime poate contine valori numerice de diferite tipuri si lungimi sau
siruri de caractere sau alte tipuri de date agregat (structuri), sau pointeri
(adrese). Ideal ar fi ca operatiile cu un anumit tip de colectie s poat fi scrise
ca functii generale, adaptabile pentru fiecare tip de date ce va face parte din
colectie. Acest obiectiv este de dorit mai ales pentru operatii care necesit
algoritmi mai complicati (operatii cu arbori binari echilibrati sau cu tabele de
dispersie, de ex.), pentru a evita rescrierea functiilor pentru fiecare nou tip de
date folosit.
Realizarea unei colectii generice n C (si n Turbo Pascal) se poate face n
dou moduri, dar nici unul complet satisfctor:
- Prin utilizarea de tipuri generice (neprecizate) pentru elementele colectiei n
subprogramele ce realizeaz operatii cu colectia. La utilizarea acestor
subprograme adaptarea lor la un tip precis, cerut de o aplicatie, se face partial
de ctre compilator (prin macro-substitutie) si partial de ctre programator
(care trebuie s dispun de forma surs pentru aceste subprograme).
- Prin utilizarea unor colectii de pointeri la un tip neprecizat (void * n C) si a
unor argumente de acest tip n subprograme, urmnd ca nlocuirea cu un alt tip
de pointer (la date specifice aplicatiei) s se fac la executie. Utilizarea unor
astfel de subprograme este mai dificil, dar utilizatorul nu trebuie s intervin
n textul surs al subprogramelor.

Functii generice standard n C


In fisierul stdlib.h sunt declarate patru functii generice pentru sortarea,
cutarea liniar si cutarea binar ntr-un vector cu componente de orice tip,
care ilustreaz o modalitate simpl de generalizare a tipului unui vector.
Argumentul formal de tip vector al acestor functii este declarat ca void* si este
nlocuit cu un argument efectiv pointer la un tip precizat (nume de vector). De
remarcat c nu se poate declara un vector cu componente void (void a []; nu e
corect).
Un alt argument al acestor functii este adresa unei functii de comparare a
unor date de tipul celor memorate n vector, functie furnizat de utilizator si
care depinde de datele folosite n aplicatia sa.

http://elth.srv.ro/

136
Pentru exemplificare urmeaz declaratiile pentru trei din aceste functii
(lfind este la fel cu lsearch):
void *bsearch (const void *key, const void *base,
size_t nelem, size_t width,
int (*fcmp)(const void*, const void*));
void *lsearch (const void *key, void *base,
size_t * pnelem, size_t width,
int (*fcmp)(const void *, const void *));
void qsort(void *base, size_t nelem, size_t width,
int (*fcmp)(const void *, const void *));

base este adresa vectorului, key este cheia (valoarea) cutat n vector
(de acelasi tip cu elementele din vector), width este dimensiunea unui
element din vector (ca numr de octeti), nelem este numarul de elemente din
vector, fcmp este adresa functiei de comparare a dou elemente din vector.
Exemplul urmtor arat cum se poate ordona un vector de numere ntregi cu
functia qsort :
// comparare numere intregi
int intcmp (const void * a, const void * b) {
return *(int*)a-*(int*)b;
}
void main () {
int a[]= {5,2,9,7,1,6,3,8,4};
int i, n=9;
//
n=dimensiune
vector
qsort ( a,9, sizeof(int),intcmp);
// ordonare
vector
for (i=0;i<n;i++)
// afisare rezultat
printf("%d ",a[i]);
}

Utilizarea de tipuri neprecizate


Primul exemplu arat cum se defineste o multime vector cu componente de
un tip neprecizat n subprograme, dar precizat n programul care foloseste
multimea :
// multimi de elemente de tipul T
typedef int T;
// tip componente multime
typedef struct {
T m[M];
// multime de intregi
int n;
// dimensiune multime
} Set;
// operatii cu o multime
int findS ( Set a, T x) { // cauta pe x in multimea a

http://elth.srv.ro/

137
int j=0;
while ( j < a.n && x != a.m[j] )
++j;
if ( j==a.n) return 0; // negasit
else return 1;
// gasit
}
int addS ( Set* pa, T x) { // adauga pe x la multimea a
if ( findS (*pa,x) )
return 0;
// nu s-a modificat multimea a
pa->m[pa->n++] = x;
return 1;
//
s-a
modificat
multimea a
}

Operatiile de citire-scriere a unor elemente din multime depind de asemenea


de tipul T, dar ele fac parte n general din programul de aplicatie.
Functiile anterioare sunt corecte numai dac tipul T este un tip numeric
(aritmetic) pentru c operatiile de comparare la egalitate si de atribuire depind
n general de tipul T. Pentru a scrie operatii cu colectii care s fie valabile
pentru orice tip T avem mai multe posibilitti:
a) Definirea unor operatori generalizati, modificati prin macro-substitutie :
#define EQ(a,b) (a == b) // equals
#define LT(a,b) (a < b)
// less than
#define AT(a,b) (a = b)
// assign to
int findS ( Set a, T x) { // cauta pe x in multimea a
int j=0;
while ( j < a.n && ! EQ(x,a.m[j]) )
++j;
if ( j==a.n) return 0;
// negasit
else return 1;
// gasit
}
int addS (Set* pa, T x) {
// adauga pe x la o multime
if ( findS (*pa,x) )
return 0;
// nu s-a modificat multimea
AT(pa->m[pa->n++],x);
// adaugare x la multime
return 1;
//
s-a
modificat
multimea
}

Pentru o multime de siruri de caractere trebuie operate urmtoarele


modificri n secventele anterioare :
#define
#define
#define
typedef

EQ(a,b) ( strcmp(a,b)==0)
LT(a,b) (strcmp(a,b) < 0)
AT(a,b) ( strcpy(a,b) )
char * T;

// equals
// less than
// assign to

http://elth.srv.ro/

138
b) Utilizarea unor functii de comparatie cu nume predefinite, care vor fi
rescrise n functie de tipul T al elementelor multimii. Exemplu:
typedef char * T;
// comparare la egalitate siruri de caractere
int comp (T a, T b ) {
return strcmp (a,b);
}
int findS ( Set a, T x) { // cauta pe x in multimea a
int j=0;
while ( j < a.n && comp(x,a.m[j]) ==0 )
++j;
if ( j==a.n) return 0;
// negasit
else return 1;
// gasit
}

c) Transmiterea functiilor de comparare, atribuire, s.a ca argumente la


functiile care le folosesc (fr a impune nume fixe acestor functii), la fel ca la
apelul functiei qsort. Exemplu:
typedef char * T;
// definire tip T
// tip functie de comparare
typedef (int *) Fcmp ( T a, T b) ;
// cauta pe x in multimea a
int findS ( Set a, T x, Fcmp cmp ) {
int j=0;
while ( j < a.n && cmp(x,a.m[j]) ==0 )
++j;
if ( j==a.n) return 0;
// negasit
else return 1;
// gasit
}

Uneori tipul T al datelor folosite de o aplicatie este un tip agregat (o


structur C): o dat calendaristic ce grupeaz numere pentru zi, lun, an ,
descrierea unui arc dintr-un graf pentru care se memoreaz numerele nodurilor
si costul arcului, s.a. Problema care se pune este dac tipul T este chiar tipul
structur sau este un tip pointer la acea structur. Ca si n cazul sirurilor de
caractere este preferabil s se lucreze cu pointeri (cu adrese de structuri) si nu
structuri. In plus, atribuirea ntre pointeri se face la fel ca si atribuirea ntre
numere (folosind operatorul de atribuire). Obiectele nu se mut n memorie, ci
doar adresele lor se mut dintr-o colectie n alta.
In concluzie, tipul neprecizat T al elementelor unei colectii este de obicei fie
un tip numeric, fie un tip pointer (inclusiv de tip void * ).

Utilizarea de pointeri la void

http://elth.srv.ro/

139
O a doua solutie pentru o colectie generic este o colectie de pointeri la orice
tip (void *), care vor fi nlocuiti cu pointeri la datele folosite n fiecare
aplicatie. Si n acest caz functia de comparare trebuie transmis ca argument
functiilor de inserare sau de cutare n colectie. Avantajul asupra solutiei cu
tip neprecizat T este acela c functiile pentru operatii cu colectii pot fi
compilate si puse ntr-o bibliotec si nu este necesar codul surs. Exemplu de
operatii cu o multime de pointeri:
// Multime ca vector de pointeri
// tipul multime
#define M 100
typedef void* Ptr;
typedef int (*Fcmp) (Ptr,Ptr) ;
typedef struct {
Ptr v[M];
int n;
// nr elem in multime
} * Set;
// afisare date din multime
void printS ( Set a) {
void print ( Ptr); // declara functia apelata
int i;
for(i=0;i<a->n;i++)
print (a->v[i]); // depinde de tipul argumentului
printf ("\n");
}
// cautare in multime
int findS ( Set a, Ptr p, Fcmp comp ) {
int i;
for (i=0;i<a->n;i++)
if (comp(p,a->v[i])==0)
return 1;
return 0;
}
// adaugare la multime
int addS ( Set a, Ptr p, Fcmp comp) {
if ( findS(a,p,comp))
return 0;
// multime nemodificata
a->v[a->n++] = p;
// adaugare la multime
return 1;
// multime modificata
}
// initializare multime
void initS (Set a) {
a->n=0;
}

Dezavantajul unor colectii de pointeri apare n aplicatiile numerice: pentru


fiecare numr trebuie alocat memorie la executie ca s obtinem o adres
distinct ce se memoreaz n colectie. Fiecare bloc de memorie alocat dinamic
are un antet cu lungimea blocului (8 octeti n Borland C). Consumul de

http://elth.srv.ro/

140
memorie este deci cu mult mai mare dect n cazul unui vector cu date de tip
neprecizat. Exemplu de creare si afisare a unei multimi de ntregi:
// utilizare multime de pointeri
// afisare numar intreg
void print ( Ptr p) {
printf ("%d ", *(int*)p );
}
// comparare de intregi
int intcmp ( void* a, void* b) {
return *(int*)a - *(int*)b;
}
void main () {
// citire numere si creare multime
Set a; int x; int * p;
initS(a);
printf ("Elem. multime: \n");
while ( scanf ("%d", &x) > 0) {
p= (int*) malloc (sizeof(int));
*p=x;
add(a,p,intcmp);
}
printS (a);
}

Tipuri abstracte de date


O multime este un tip abstract de date definit ca o colectie de valori distincte
si are ca operatie specific verificarea apartenentei unei valori la o multime
(deci o cutare n multime dup valoare ). In plus, sunt aplicabile operatii
generale cu orice colectie : initializare, adugare element la colectie, eliminare
element din colectie, determinare dimensiune colectie, afisare colectie, s.a.
Tipul abstract multime poate fi definit printr-un vector de valori sau
printr-un vector de biti (numai multimi de ntregi) sau printr-o list nlntuit
sau printr-un arbore sau printr-un tabel de dispersie. Anumite implementri
permit si multimi ordonate, dar altele nu permit dect multimi neordonate.
Problema care se pune este ca o aplicatie care foloseste multimi s nu
necesite modificri la schimbarea implementarii tipului abstract multime, sau
sa necesite ct mai putine modificri. Programul de aplicatie trebuie s arate
la fel (cu exceptia unor definitii de tip), indiferent care este structura fizic
pentru tipul abstract multime.
In limbajele procedurale definirea unor tipuri abstracte de date se face fie
prin definirea si utilizarea unor tipuri structur (nregistrare) cu acelasi nume,
dar cu implementri diferite, fie prin utilizarea de pointeri la un tip nedefinit.
In limbajele orientate pe obiecte exist alte solutii, mai simple pentru lucrul cu
colectii generice si colectii abstracte.
In limbajul C nu este posibil s avem n acelasi program functii cu acelasi

http://elth.srv.ro/

141
nume dar care difer prin tipul sau numrul argumentelor (dar in C++ este
posibil, prin supradefinirea unor functii).
O solutie C pentru a folosi colectii abstracte este crearea de fisiere antet (de
tip .h) pentru definirea tipurilor folosite (cu declaratii typedef) si crearea de
biblioteci separate pentru diferite implementri.
Functiile urmtoare folosesc o list simplu nlntuit pentru implementarea
unei multimi neordonate de pointeri la date de orice tip.
typedef struct snod { // nnod de lista nlntuita
Ptr pd;
// adresa date
struct snod *next;
} nod,* pnod;
typedef pnod Set;
// Set este un tip pointer !
// cautare in multime
int findS ( Set a, Ptr p, Fcmp comp ) {
while (a)
if (comp(p,a->pd)==0)
return 1;
else
a=a->next;
return 0;
}
// adaugare la multime
int add ( Set *a, Ptr p, Fcmp comp) {
nod * nou;
if ( findS(*a,p,comp))
return 0;
// multime nemodificata
// adaugare la inceput de lista
nou=(nod*) malloc(sizeof(nod));
nou->pd=p; nou->next=*a; // nou devine ultimul nod
*a=nou;
return 1;
// multime modificata
}
void main () {
// citire numere si creare multime
Set a; int x; int * p;
initSet(&a);
printf ("Elem. multime: \n");
while ( scanf ("%d", &x) > 0) {
p= (int*) malloc (sizeof(int));
*p=x;
add(&a,p,intcmp);
printS (a);
}
printS (a);
}

O solutie pentru folosirea de functii cu acelasi nume pentru operatii cu


structuri de date diferite poate fi utilizarea tipului generic void* si pentru tipul
colectiei, dup modelul functiilor generice standard (qsort ,bsearch s.a.).

http://elth.srv.ro/

142
In interiorul fiecarei functii se face o conversie de la tipul generic void* la
tipul pointer specific colectiei folosite (vector, list sau altceva). De observat
c o functie care modific un pointer trebuie s primeasc un pointer la pointer
sau nu mai are tipul void. Aceast solutie poate duce ns la functii greu de
scris si de nteles.
Exemplu de operatii cu o stiv vector ce poate contine date de orice tip:
#define MAX 100
typedef struct {
int sp;
void* elem[MAX];
} stiva, * ptrSt;
void initSt (void* ps) {
ptrSt p =(ptrSt)ps;
p->sp=0;
}
int push ( void* ps, void* e) {
ptrSt p =(ptrSt)ps;
if ( p->sp ==MAX) return 0;
// stiva plina
p->elem[p->sp ++] =e;
return 1;
}
// conversie in binar, cu stiva
void binar (int n) {
stiva st; int *pb; void ** pv;
initSt (& st);
while (n > 0) {
pb = (int*) malloc (sizeof(int));
*pb= n % 2 ;
// o cifra binara
push( &st,pb);
// memoreaza rest in stiva
n= n / 2;
}
while (! emptySt(&st)) {
pop( &st, pv);
// scoate din stiva
int * pb = *(int**) pv;
printf ("%d",* pb);
// si afiseaza
}
}

Compilatorul nu poate face prea multe verificri asupra programelor cu


pointeri, mai ales atunci cnd se folosesc pointeri fr tip (void *) la structuri
de date opace.

http://elth.srv.ro/

143

14. Diferente ntre limbajele C si C++


Diferente de sintax
Limbajul C++ este o extindere a limbajului C pentru programare orientat pe
obiecte. Limbajul C++ aduce o serie de inovatii fat de limbajul C standard
care nu sunt legate direct de aparitia claselor: alt fel de comentarii, noi
operatori, noi tipuri de date (referinte), noi reguli sintactice s.a.
Cu ocazia adugrii facilittilor necesare POO s-au mai adus si alte
mbunttiri limbajului C, astfel nct C++ este un "C ceva mai bun".
Cu mici exceptii, exist compatibilitate ntre cele dou limbaje, n sensul c
un program C este acceptat de compilatorul C++ si produce aceleasi rezultate
la executie. Unele compilatoare trateaz continutul unui fisier surs n functie
de extensia la numele fisierului: fisierele cu extensia CPP contin programe
C++, iar fisiere cu orice alt extensie se consider a fi scrise n C.
O parte dintre inovatiile aduse sunt importante si pentru cei care nu folosesc
clase n programele lor.
In C++ se prefer alt definire pentru constante simbolice,n loc de directiva
#define, care permite verificri de tip din partea compilatorului. Exemplu:

http://elth.srv.ro/

144
const int NMAX=1000;
// fr "static" n C++
void main () {
int n, x[NMAX], y[NMAX];
. . .

In C++ declaratiile de variabile sunt tratate la fel cu instructiunile si deci pot


apare oriunde ntr-un bloc; n C declaratiile trebuie s precead prima
instructiune executabil dintr-un bloc. Se poate vorbi chiar un alt stil de
programare, n care o variabil este declarat acolo unde este folosit prima
dat. Exemplu:
// suma valorilor dintr-un vector
float sum (float x[], int n ) {
float s=0;
for ( int i=0; i<n; i++)
s += x[i];
return s;
}

Domeniul de valabilitate al variabilei este blocul unde a fost declarat


variabila, dar instructiunea for prezint un caz special. In versiunile mai noi
ale limbajului C++ domeniul de valabilitate al variabilei declarate ntr-o
instructiune for este limitat la instructiunile care vor fi repetate (din cadrul
ciclului for). Din acest motiv secventa urmtoare poate produce sau nu erori
sintactice:
...
for (int i=0;i<6;i++) a[i]=i;
for (int i=0;i<6;i++) printf ("%d ", a[i];

In C++ se admite folosirea numelui unui tip structur, fr a fi precedat de


struct si fr a mai fi necesar typedef. Exemplu:
struct nod {
int val;
nod * leg;
};
nod * lista;

// in C: struct nod * leg


// in C: struct nod * lista

Diferente la functii
In C++ toate functiile folosite trebuie declarate si nu se mai consider c o
functie nedeclarat este implicit de tipul int. Dar o functie definit fr un tip
explicit este considerat ca fiind de tip int. Asa se explic de ce functia main
este deseori declarat ca fiind de tip void; absenta cuvntului void implic

http://elth.srv.ro/

145
tipul int pentru functia main si compilatorul verific existenta unei instructiuni
return cu expresie de tip ntreg.
In C++ se pot declara valori implicite pentru parametri formali de la
sfrsitul listei de parametri; aceste valori sunt folosite automat n absenta
parametrilor efectivi corespunztori la un apel de functie. O astfel de functie
poate fi apelat deci cu un numr variabil de parametri. Exemplu:
// afisare vector, precedata de un titlu
void printv ( int v[], int n, char * titlu="") {
// afiseaza sirul primit sau sirul nul
printf ("\n %s \n", titlu);
for (int i=0; i<n; i++)
printf ("%d ", v[i]);
}
...
// exemple de apeluri
printv ( x,nx );
// cu 2 parametri
printv (a,na," multimea A este");
// cu 3 parametri

In C++ functiile scurte pot fi declarate inline, iar compilatorul nlocuieste


apelul unei functii inline cu instructiunile din definitia functiei, eliminnd
secventele de transmitere a parametrilor. Functiile inline sunt tratate ca si
macrourile definite cu define.
Orice functie poate fi declarat inline, dar compilatorul poate decide c
anumite functii nu pot fi tratate inline si sunt tratate ca functii obsnuite. De
exemplu, functiile care contin cicluri nu pot fi inline. Utilizarea unei functii
inline nu se deosebeste de aceea a unei functii normale. Exemplu de functie
inline:
inline int max (int a, int b) { return a>b ? a : b; }

In C++ pot exista mai multe functii cu acelasi nume dar cu parametri diferiti
(ca tip sau ca numr). Se spune c un nume este "suprancrcat" cu
semnificatii ("function overloading").
Compilatorul poate stabili care din functiile cu acelasi nume a fost apelat
ntr-un loc analiznd lista de parametri si tipul functiei. Exemple:
float abs (float f) { return fabs(f); }
long abs (long x) { return labs(x); }
printf ("%6d%12ld
%f \n", abs(-2),abs(-2L),abs(-2.5)
);

Supradefinirea se practic pentru functiile membre (din clase) si, n


particular, pentru operatori definiti fie prin functii membre, fie prin functii
prietene.

http://elth.srv.ro/

146

Operatori pentru alocare dinamic


In C++ s-au introdus doi operatori noi, pentru alocarea dinamic a memoriei
new si pentru eliberarea memoriei dinamice delete, destinati s nlocuiasc
functiile de alocare si eliberare (malloc, free, s.a.). Operatorul new are ca
operand un nume de tip, urmat n general de o valoare initial pentru variabila
creat (ntre paranteze rotunde); rezultatul lui new este o adres (un pointer de
tipul specificat) sau NULL daca nu exist suficient memorie liber.
Exemple:
nod * pnod;
// pointer la nod de lista
pnod = new nod;
// alocare fara initializare
assert (pnod != NULL);
int * p = new int(3); // alocare cu initializare

Operatorul new are o form putin modificat la alocarea de memorie pentru


vectori, pentru a specifica numrul de componente. Exemplu:
int * v = new int [n];

// vector de n intregi

Operatorul delete are ca operand o variabil pointer si are ca efect eliberarea


blocului de memorie adresat de pointer, a crui mrime rezult din tipul
variabilei pointer sau este indicat explicit. Exemple:
int * v;
delete v;
delete [] v;
delete [n] v;

// elibereaza sizeof(int) octeti


// elibereaza n*sizeof(int) octeti

Operatorul de rezolutie "::" este necesar pentru a preciza domeniul de nume


cruia i apartine un nume de variabil sau de functie.
Fiecare clas creeaz un domeniu separat pentru numele definite n acea
clas (pentru membri clasei). Deci un acelasi nume poate fi folosit pentru o
variabila extern (definit n afara claselor), pentru o variabil local unei
functii sau pentru o variabil membr a unei clase (structuri). Exemplu:
int end;
// variabila externa
void cit () {
int end=0;
// variabila locala
...
if (::end) { ...} // variabila externa
}
class A {
public:
int end;
// variabila membru a clasei A

http://elth.srv.ro/

147
void print();
...
};
// exemple de utilizare in "main"
end=1;
// sau
A::end=0;
f.seekg (0, ios::end);
// din clasa predefinita
"ios"
...

Utilizarea operatorului de rezolutie este necesar si la definirea metodelor


unei clase n afara clasei, pentru a preciza compilatorului c este definitia unei
metode si nu definitia unei functii externe. Exemplu:
//
void
//
void

definitie metoda din clasa A


A:: print () { ... }
definitie functie externa
print () { ... }

Tipuri referint
In C++ s-au introdus tipuri referint, folosite n primul rnd pentru parametri
modificabili sau de dimensiuni mari. Si functiile care au ca rezultat un obiect
mare pot fi declarate de un tip referint, pentru a obtine un cod mai
performant. Caracterul ampersand (&) folosit dup tipul si naintea numelui
unui parametru formal (sau unei functii) arat compilatorului c pentru acel
parametru se primeste adresa si nu valoarea argumentului efectiv. Exemplu:
// schimba intre ele doua valori
void schimb (int & x, int & y) {
int t = x;
x = y; y = t;
}
// ordonare vector
void sort ( int a[], int n ) {
...
if ( a[i] > a[i+1])
schimb ( a[i], a[i+1]);
...
}

Spre deosebire de un parametru pointer, un parametru referint este folosit


de utilizator n interiorul functiei la fel ca un parametru transmis prin valoare,
dar compilatorul va genera automat indirectarea prin pointerul transmis (n
programul surs nu se foloseste explicit operatorul de indirectare '*').

http://elth.srv.ro/

148
Referintele simplific utilizarea unor parametri modificabili de tip pointer,
eliminnd necesitatea unui pointer la pointer. Exemplu:
void initL (nod* & cap) {
cap=new nod;
nod->leg=NULL:
}

// initializare lista
// cap este un pointer

Sintaxa declararii unui tip referint este urmtoarea:


tip & nume
unde "nume" poate fi:
- numele unui parametru formal
- numele unei functii (urmat de lista argumentelor formale)
- numele unei variabile (mai rar)
Efectul caracterului '&' n declaratia anterioar este urmtorul: compilatorul
creeaz o variabil "nume" si o variabil pointer la variabila "nume",
initializeaz variabila pointer cu adresa asociat lui "nume" si retine c orice
referire ulterioar la "nume" va fi tradus printr-o indirectare prin variabila
pointer anonim creat.
O functie poate avea ca rezultat o referint la un vector dar nu poate avea ca
rezultat un vector.
O functie nu poate avea ca rezultat o referint la o variabila local, asa cum
nu poate avea ca rezultat un pointer la o variabila local. Exemplu:
typedef int Vec [M];
// adunarea a 2 vectori - gresit !
Vec& suma (Vec a, Vec b, int n) {
Vec c;
for (int i=0; i<n;i++)
c[i]=a[i]+b[i];
return c;
// eroare !!!
}

Fluxuri de intrare-iesire
In C++ s-a introdus o alt posibilitate de exprimare a operatiilor de citirescriere, pe lng functiile standard de intrare-iesire din limbajul C. In acest
scop se folosesc cteva clase predefinite pentru "fluxuri de I/E" (declarate n
fisierele antet <iostream.h> si <fstream.h> ).
Un flux de date ("stream") este un obiect care contine datele si metodele
necesare operatiilor cu acel flux. Pentru operatii de I/E la consol sunt definite
variabile de tip flux, numite "cin" (console input), "cout" (console output).

http://elth.srv.ro/

149
Operatiile de citire sau scriere cu un flux pot fi exprimate prin metode ale
claselor flux sau prin doi operatori cu rol de extractor din flux (>>) sau
insertor n flux (<<). Atunci cnd primul operand este de un tip flux,
interpretarea acestor operatori nu mai este cea de deplasare binar ci este
extragerea de date din flux (>>) sau introducerea de date n flux (<<).
Operatorii << si >> implic o conversie automat a datelor ntre forma
intern (binar) si forma extern (sir de caractere). Formatul de conversie
poate fi controlat prin cuvinte cheie cu rol de "modificator". Exemplu de
scriere si citire cu format implicit:
#include <iostream.h>
void main ( ) {
int n; float f; char
cout << " n= "; cin
cout << " f= "; cin
cout << " un sir: ";
}

s[20];
>> n;
>> f;
cin >> s;

cout << s << "\n";

Intr-o expresie ce contine operatorul << primul operand trebuie s fie "cout"
(sau o alt variabil de un tip "ostream"), iar al doilea operand poate s fie de
orice tip aritmetic sau de tip "char*" pentru afisarea sirurilor de caractere.
Rezultatul expresiei fiind de tipul primului operand, este posibil o expresie cu
mai multi operanzi (ca la atribuirea multipl). Exemplu:
cout << "x= " << x << "\n";

este o prescurtare a secventei de operatii:


cout << "x= "; cout << x; cout << "\n";

In mod similar, ntr-o expresie ce contine operatori >> primul operand


trebuie s fie "cin" sau de un alt tip "istream", iar ceilalti operanzi pot fi de
orice tip aritmetic sau pointer la caractere. Exemplu:
cin >> x >> y;

este echivalent cu secventa:


cin >> x; cin >> y;

Operatorii << si >> pot fi ncrcati si cu alte interpretri, pentru scrierea


sau citirea unor variabile de orice tip clas, cu conditia supradefinirii lor .
Este posibil si un control al formatului de scriere prin utilizarea unor
modificatori.

Tipuri clas
Tipurile clas reprezint o extindere a tipurilor structur si pot include ca
membri variabile si functii. Pentru definirea unei clase se poate folosi unul din

http://elth.srv.ro/

150
cuvintele cheie class, struct sau union, cu urmtoarele efecte asupra atributelor
de accesibilitate ale membrilor clasei:
- O clas definit prin class are implicit toti membri invizibili din afara clasei
(de tip private).
- O clasa definit prin struct sau union are implicit toti membri publici,
vizibili din afara clasei.
In practic avem nevoie ca datele clasei s fie ascunse (locale) si ca functiile
clasei s poat fi apelate de oriunde (publice). Pentru a stabili selectiv nivelul
de acces se folosesc cuvintele cheie public, private si protected, ca etichete de
sectiuni cu aceste atribute, n cadrul unei clase.
In mod uzual, o clas are dou sectiuni: sectiunea de date (private) si
sectiunea de metode (public).
Functiile unei clase, numite si metode ale clasei, pot fi definite complet n
cadrul definitiei clasei sau pot fi numai declarate n clas si definite n afara ei
Exemplul urmtor contine o variant de definire a unei clase pentru un vector
extensibil de numere ntregi:
class intArray {
// clasa vector de intregi
// date clasei (private)
int * arr;
// adresa vector (alocat dinamic)
int d,dmax,inc; // dimensiune curenta si maxima
void extend();
// implicit private, definita
ulterior
public:
intArray (int max=10, int incr=10){ // constructor
dmax=max; inc=incr; d=0;
arr= new int[dmax];
}
~intArray () { delete [] arr;}
// destructor
int get (int i)
{ assert (i >= 0 && i < dmax);
return arr[i];
}
void add (int elem) {
if ( d==dmax)
extend();
arr[d++]=elem;
}
int size() { return d; } // dimensiune curenta vector
};
// extindere vector
void intArray::extend () {
int * oldarr=arr;
dmax+=inc;
arr = new int[dmax];
for (int i=0;i<d;i++)
arr[i]= oldarr[i];
delete [] oldarr;

http://elth.srv.ro/

151
}

Pentru clasele folosite n mai multe aplicatii, cum este clasa intArray, se
recomand ca toate functiile clasei s fie definite n afara clasei, ntr-un fisier
surs separat; eventual se compileaz si se introduc ntr-o bibliotec. Definitia
clasei se pune ntr-un fisier antet separat, care va fi inclus de toate fisierele
surs ce folosesc tipul respectiv. In acest fel este separat descrierea clasei de
implementarea clasei si de utilizrile clasei n diverse aplicatii. Exemplu:
// fisier INTARRAY.H
class intArray {
private:
int * arr;
// adresa vector (alocat dinamic)
int d,dmax,inc; // dimensiune curenta si maxima
void extend();
// implicit private, definita
ulterior
public:
intArray (int max=10, int incr=10); // constructor
~intArray ();
// destructor
int get (int ) ;
// extrage element
void add (int );
// adauga element
int size();
// dimensiune vector
};
// fisier INTARRAY.CPP
#include intArray.h
intArray::intArray (int max=10, int incr=10){
dmax=max; inc=incr; d=0;
arr= new int[dmax];
}
intArray::~intArray () { delete [] arr;}
intArray::int get (int i)
{ assert (i >= 0 && i < dmax);
return arr[i];
}
void intArray::add (int elem) {
if ( d==dmax)
extend();
arr[d++]=elem;
}
int intArray::size() {
return d;
}
// fisier care foloseste clasa intArray : TEST.CPP
#include intArray.h
#include <iostream.h>
void main () {
intArray a(3,1); // iniial 3 elemente, increment 1
for (int i=1;i<=10;i++)
a.add(i);

http://elth.srv.ro/

152
for (i=0;i< a.size();i++)
cout << a.get(i) << ' ';
cout << endl;
}

Orice clas are (cel putin) o functie constructor (public) apelat implicit la
definirea de variabile de tipul clasei; un constructor aloc memorie si
initializeaz variabilele clasei. Functiile constructor au toate numele clasei si
pot diferi prin lista de argumente. O functie constructor nu are tip.
O functie destructor este necesar numai pentru clase cu date alocate
dinamic (n constructor).
Sintaxa pentru apelul unei metode (nestatice) extinde referirea la membri
unei structuri si se interpreteaz ca apel de functie pentru un obiect dat prin
numele su.

Supradefinirea operatorilor
Pentru variabilele de un tip clas (structur) se pot folosi numai doi
operatori, fr a mai fi definiti. Acestia sunt operatorul de atribuire ('=') si
operatorul de obtinere a adresei variabilei ('&'). La atribuirea ntre variabile de
un tip clas se copiaz numai datele clasei .
Alte operatii cu obiecte se definesc prin functii si/sau operatori specifici
clasei respective.
Operatorii limbajului C pot fi supradefiniti, adic pot fi asociati si cu alte
operatii aplicate unor variabile de tip clas. Aceast facilitate este util n
cazul claselor de definesc noi tipuri de date.
Un operator este considerat n C++ ca o functie cu un nume special, dar
supus tuturor regulilor referitoare la functii. Numele unei functii operator
const din cuvntul operator urmat de unul sau dou caractere speciale, prin
care se foloseste operatorul. In exemplul urmtor se defineste o clas pentru
siruri de caractere, cu un operator de concatenare siruri (+).
class string {
char * start; // adresa sir terminat cu zero
public:
string ( char * s); // un constructor
string () { start=new char[80]; *start='\0';}
~string() {delete start; }
string& operator + (string& sir);
void show (void) { cout << start << '\n';}
};
// functii ale clasei 'string'
string::string ( char * s) {
int lung= strlen(s);

http://elth.srv.ro/

153
start=new char[lung+1];
strcpy (start,s);
}
string& string::operator + (string& str) {
int lung=strlen(start)+strlen(str.start);
char * nou=new char[lung+1];
strcpy (nou,start);
strcat (nou,str.start);
delete start; start=nou;
return * this;
}
// teste
main () {
string s1 ("zori "), s2 ("de "), s ;
s= s1+s2; s.show();
// concatenare siruri
string s3 ("zi");
s= s1+s2+s3; s.show();
}

In locul metodei show care afiseaz un sir se poate supradefini operatorul


de insertie n flux >> pentru a putea fi folosit cu operand de tip string, iar
pentru clasa intArray se poate supradefini operatorul [] astfel ca s putem
scrie a[k] n locul functiei get(k) pentru acces la elementul din pozitia k.
Variabilele de un tip clas se numesc si obiecte sau instantieri ale clasei.
Variabilele unei clase sunt multiplicate n fiecare obiect si au n general valori
diferite pentru obiecte diferite. De exemplu, variabilele s1 si s2 au ca
valori adrese diferite de siruri.
Cuvntul cheie this se poate folosi numai n metodele (nestatice), este de tip
pointer si desemneaz adresa obiectului pentru care se execut metoda.
Expresia *this desemneaz obiectul curent.

http://elth.srv.ro/

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