Sunteți pe pagina 1din 54

Limbajul de programare C

- note de curs -(c)

(c) )
1991-2001 Lucian Cucu – suport de curs pentru studentii sectiei de Informatica a Facultatii de Matematica,
Universitatea de Vest Timisoara
INTRODUCERE: PRELIMINARII............................................................................................................ 4
Modelul functional (simplificat) al unui calculator:.........................................................................................4
Etapele rezolvarii unei probleme cu calculatorul:............................................................................................5
Limbajul de programare C: Istoric. Legatura cu sistemul de operare UNIX...............................................5
Structura programelor C. Elemente constitutive.............................................................................................6

DATE...................................................................................................................................................... 8
Tipuri de date de baza in C...............................................................................................................................10
Specificarea constantelor...................................................................................................................................10
Declaratii.............................................................................................................................................................11
Declaratii de variabile (sintaxa).........................................................................................................................13
Clase de memorare.............................................................................................................................................13
Domeniul unui identificator..............................................................................................................................14
Vizibilitatea.........................................................................................................................................................14
Durata.................................................................................................................................................................14
Modificatori de declaratii..................................................................................................................................15
Initializarea variabilelor pe linia de declaratie...............................................................................................15

EXPRESII SI OPERATORI................................................................................................................... 16
Operatori.............................................................................................................................................................17
Conversii de tip..................................................................................................................................................20
Conversii implicite de tip...................................................................................................................................22
Conversii explicite de tip...................................................................................................................................23

CONTROLUL EXECUTIEI (INSTRUCTIUNI).......................................................................................24


Instructiuni de test.............................................................................................................................................24
Instructiunea de selectie....................................................................................................................................25
Instructiuni de ciclare........................................................................................................................................26
Instructiuni de control a ciclurilor...................................................................................................................27
Instructiunea de revenire din apel de functie.................................................................................................28
Instructiunea de salt neconditionata................................................................................................................28

STRUCTURA PROGRAMELOR. FUNCTII.......................................................................................... 29


Transmiterea argumentelor. Contextul de apel. Conventia de apel C.........................................................30
Comunicarea intre functii.................................................................................................................................30
Gestionarea aplicatiilor complexe. Proiecte....................................................................................................34
Initializarea variabilelor....................................................................................................................................35
Initializarea tablourilor.....................................................................................................................................35
Functii recursive.................................................................................................................................................36
Preprocesorul C.................................................................................................................................................37

POINTERI............................................................................................................................................. 40
Adrese..................................................................................................................................................................40
Pointerii sunt variabile. Declararea variabilelor pointer..............................................................................40
Operatorul &. Initializarea pointerilor. De ce?....................................................................................................41
Dereferenterea pointerilor. Operatorul *........................................................................................................41
Pointeri (void *)..................................................................................................................................................41
Pointeri ca argumente de functii.......................................................................................................................41
Operatii cu pointeri. Pointeri si tablouri. Indexarea si dereferentierea – notatii echivalente....................42

2
Pointeri si siruri de caractere............................................................................................................................42
Pointeri vs. tablouri............................................................................................................................................42
Eficienta utilizarii pointerilor...........................................................................................................................43
Pointeri la functii..................................................................................................................................................43
Tablouri multidimensionale. Tablouri de pointeri.........................................................................................43
Transmiterea argumentelor pe linia de comanda...........................................................................................44

STRUCTURI SI UNIUNI........................................................................................................................ 45
Structuri. Declaratie..........................................................................................................................................45
Operatii cu structuri. Accesul la membri........................................................................................................45
Structuri imbricate............................................................................................................................................46
Pointeri la structuri. Tablouri de structuri. Tablouri de pointeri la structuri.............................................46
Structuri cu autoreferire. Liste, arbori, tabele de cautare (hashing)............................................................47
Operatorul typedef.............................................................................................................................................48
Uniuni. Declaratie..............................................................................................................................................48
Operatii cu uniuni..............................................................................................................................................49

PROBLEME CARE IMPLICA UTILIZAREA TABLOURILOR SAU A LISTELOR...............................50

INTRARI SI IESIRI (I/O). FISIERE........................................................................................................ 51


Intrari/iesiri standard........................................................................................................................................51
Fisiere. Modele...................................................................................................................................................51
Modelul cu streamuri. Operatii tipice cu streamuri.......................................................................................51
Clasificare a functiilor standard de I/O (modelul cu streamuri)...................................................................52

3
Introducere: preliminarii

Modelul functional (simplificat) al unui calculator:

 procesorul (unitatea de...calcul, registri pt operanzi/instructiuni, etc)

 memoria, pt stocarea datelor( operanzi ) si codului (instructiuni)

 dispozitive de I/O (pt "introducerea" si "extragerea programului/datelor)

A
L
U

Procesorul este locul/dispozitivul in care se efectueaza “calculele”. Operanzii, pastrati in registrii de date sunt
prelucrati conform instructiunii din registrul de instructiuni, rezultatul prelucrarii fiind depus intr-unul din
registrii de date.
Problema: numarul registrilor de date este limitat, de regula la cateva zeci, ori, cel mai adesea, rezolvarea unei
probleme implica mai multe date si rezultate partiale. In plus instructiunile/comenzile care descriu modul de
rezolvare pot fi la randul lor de ordinul miilor. Este evident necesara existenta unui spatiu pentru “depozitarea”
datelor si a instructiunilor pana in momentul cand sunt necesare in registri.

Memoria este locul dispzitivul in care se pastreaza datele si instructiunile unui program care se
“rezolva”(executa). Memoria este organizata in locatii capabile sa stocheze unitati de informatie (date sau
instructiuni). O data sau instructiune poate sa constea din una sau mai multe astfel de unitati de informatie,
stocate in locatii consecutive. Fiecare locatie de memorie are o adresa ( numarul de ordine fata de inceputul
memoriei). Regasirea unei date sau cod de instructiune in memorie se face pe baza adresei sale. Registrul de
adrese (din processor) este cel in care se incarca adresa datei sau a instructiunii care urmeaza sa fie aduse din
memorie intr-unul din registrii de date sau in registrul de instructiuni.

4
Problema: cum ajung in memorie datele si codurile de instructiuni care descriu un algoritm? Si cum se pot
“vedea” rezultatele programului? Penru aceasta sunt necesare dispozitivele de Intrare/Iesire (Input/Output – I/O).

Dispozitivele de I/O. Dispozitivele de intrare permit introducerea atat a succesiunii de coduri de instructiuni care
descriu algoritmul cat si a datelor problemei ce urmeaza a fi rezolvate conform acelui algoritm. Dispozitivele de
iesire au in principal rolul de a permite vizualizarea rezultatelor programului.

Etapele rezolvarii unei probleme cu calculatorul:

 alegere model/algoritm pentru precizarea solutiei

 editarea fisierului sursa (in limbaj sursa!)

 compilarea sursei si obtinerea fisierului cu codul obiect

o corectii (daca au fost erori de compilare)

 linkeditare (legarea cu alte module obiect)

o corectii (daca au fost erori de linkeditare)...

 testarea programului

Limbajele de programare (si modelul simplu de calculator):

Descrierea si manipularea datelor, respectiv a codului.

Date: constante si variabile (reprezentare si utilizare)...

Limbajul de programare C: Istoric. Legatura cu sistemul de operare UNIX.

C-ul a aparut cu ocazia dezvoltarii noului sistem de operare UNIX pentru calculatorul PDP-11. Dennis
Ritchie si ulterior Brian Kernigham, in cautarea celui mai potrivit limbaj pentru scrierea sistemului de operare au
conceput in cele din urma (la inceputul anilor ’70) un limbaj nou: C. Filiatia: CPL, B, BCPL, C.

Peste 90% din sistemul de operare UNIX a fost scris in acest limbaj nou, restul (in general driverele de
dispozitive) fiind scris in limbaj de asamblare.

Locul 'C'-ului in ierarhia limbajelor de programare


- limbaje de nivel inalt (ALGOL, FORTRAN, Pascal, Lisp, etc)

- C

- Limbaje de asamblare

- cod masina

5
Avantajele C-ului
- dimensiunea redusa:
- 27 cuvinte cheie
- inexistenta instructiunilor de I/O sau a celor pentru operatii matematice mai complicate
- structura lejera: orice functie de biblioteca poate fi rescrisa
- viteza: codul este foarte eficient
- slab tipizat: programatorul are libertate maxima pentru tratarea datelor in functie de necesitati
- limbaj structurat (chiar inainte de aparitia programarii structurate ca moda)
- permite (incurajeaza) programarea modulara
- interfata simpla cu limbjele de asamblare
- operatori pe biti
- variabile pointer
- structuri flexibile: tablourile sunt unidimensionale (dar pot consta din elemente de orice tip, inclusiv
…tablouri)
- utilizarea eficienta a memoriei
- portabilitate sporita
- biblioteci de functii speciale

Dezavantaje
- faptul ca este slab tipizat => posibilitate sporita de erori datorita conversiilor necontrolate
(trunchieri)
- controlul in timpul executiei este foarte sumar (de ex. nu se verifica depasirea limitelor unui tablou)

Aspectele esentiale in aprecierea unui limbaj de programare:


1. calitatea compilarii si in general a procesului de dezvoltare a unui program
2. calitatea codului rezultat (viteza de executie, fiabilitatea,

Standardul ANSI C
Adoptat in 1989 (pornind de la editia a doua a cartii lui B. Kernigham si D. Ritchie: The C Programming
Language) standardul ANSI C se conformeaza spiritului:

- ai incredere in programator
- nu-l impiedica pe programator sa faca ceea ce trebuie facut
- pastreaza limbajul "mic si simplu"

Structura programelor C. Elemente constitutive

Program (dupa Wirth):


Structuri de date
+
algoritm

6
Un program C este alcatuit din urmatoarele elemente constitutive:
- directive de preprocesare (precompilare)
- declaratii:
- de variabile
- de functii
- definitii de functii
- comentarii

Specificarea unui program sursa (succesiune de secvente de caractere - litere, cifre, semne speciale - care
identifica datele si instructiunile) se face prin intermediul atomilor lexicali (tokens).

Reprezentarea unui algoritm intr-un limbaj de programare se face cu ajutorul elementelor recunoscute de
analizorul lexical - parte a compilatorului - specific pentru acel limbaj de programare.

Pornind de la definitia data de N.Wirth unui program:

Program = Structuri de date + Algoritm

urmatoarele elemente (atomi lexicali sau tokeni) trebuie recunoscute de catre analizorul lexical:
 cuvintele cheie, care descriu de regula instructiunile limbajului, dar si alte elemente, ca de exemplu
denumirile tipurilor de date. Se pot include in aceasta categorie si operatorii, chiar daca nu sunt propriu-zis
cuvinte cheie, pentru ca descriu operatii
 constantele
 identificatorii, utilizati pentru denumirea variabilelor, functiilor, etichetelor
 separatori

.
Ultimele doua clase de atomi lexicali din enumerarea de mai sus sunt cei cu ajutorul carora se descrie partea de
"date" (in sensul de materie prima asupra careia se actioneaza sau care rezulta) a unui program.

7
Date
Datele cu care opereaza un program C pot face obiectul mai multor tipuri de clasificari:

Date (ca operanzi)

 constante
o literale ("as is")
o simbolice("as defined")
 variabile

Date (ca mod de reprezentare)

 intregi
o cu semn (reprezentare interna: complement fata de 2)
o fara semn (reprezentare interna: baza 2)
 reale (reprezentare interna: virgula mobila)

Date (ca mod de interpretare)

 numerice
 caractere
 adrese

Date (sub aspectul multiplicitatii)

 scalare
 agregate
o tablouri
o structuri
o uniuni
o bit-fields (campuri de biti)

Tipuri speciale de date

 siruri de caractere
 enumerari

Diferentierea intre tipurile de date se poate face

 in functie de modul de reprezentare (intregi vs. reale, complement fata de doi vs. virgula flotanta)
 in functie de interpretare (intregi vs. caractere, intregi vs.enumerari, intregi fara semn vs. pointeri)

Datele intregi sunt reprezentate in complement fata de doi. Din plaja totala de valori reprezentabile pe un
numar dat de biti, prima jumatate este rezervata valorilor pozitive iar cealalta jumatate (se recunosc dupa faptul
ca bitul cel mai semnificativ este egal cu 1) numerelor negative.

8
Reprezentare Valoare ca Valoare ca
binara numar fara semn numar cu semn

0000 0 0
0001 1 1
0010 2 2
>= 0 0011 3 3
0100 4 4
0101 5 5
0110 6 6
0111 7 7
1000 8 -8
1001 9 -7
1010 10 -6
1011 11 -5
< 0 1100 12 -4
1101 13 -3
1110 14 -2
1111 15 -1

Modalitatea de reprezentare se numeste in complement fata de 2 pentru ca reprezentarea interna a numerelor


negative (pe n biti) se obtine prin scaderea valorii absolute a numarului din 2 n. Cu alte cuvinte, reprezentarea
unui numar negativ reprezinta “complementul” (valoarea) ce trebuie adaugata valorii absolute pentru a obtine
puterea a n-a a lui 2.
De ex., daca n=4, numarul de reprezentat –5, atunci

10000- 24
0101 |-5| (adica 5)
------
1011 -5 (in reprezentarea in complement fata de 2)

Datele reale, caracterizate prin aceea ca pe langa o parte intreaga au (pot avea) si o parte fractionara, ridica
cateva probleme. Intrucat in gama de probleme de rezolvat prin utilizarea calculatorului pot apare atat numere
foarte mari (cu partea intreaga foarte mare) cat si numere foarte mici (cu parte fractionara reclamand multe pozitii
pentru a reprezenta cifrele semnificative), pentru a impaca ambele situatii ar fi necesar un spatiu mare (dublu) de
memorie pentru reprezentarea numerelor reale cel putin la nivelul numerelor intregi. In aceasta situatie insa
operatiile de manipulare (transfer memorie in/din registri) a acestor date ar fi foarte costisitoare ca timp si nici nu
s-ar justifica in majoritatea situatiilor. De exemplu, pentru numere cu cateva cifre la partea intreaga si cateva la
partea fractionara, nu ar fi necesar sa se risipeasca 64 de biti. Mai mult, in functie de problema concreta, de
obicei apar ca operanzi si rezultate numere din aceeasi "categorie": fie numere mari la care partea fractionara
lipseste sau este semnificativa doar pe primele pozitii de dupa virgula, fie numere mici, cu multe cifre
semnificative la partea fractionara si doar cateva la partea intreaga.
Solutia aleasa pentru reprezentarea numerelor reale presupune "mutarea"virgulei care desparte partea intreaga de
cea fractionara in functie de necesitati astfel ca pe un numar dat de pozitii sa se poata reprezenta si cel mai mare
numar intreg (fara cifre semnificative la partea fractionara) si cel mai mic numar subunitar ( fara parte intreaga
sau cu un numar minim de cifre la partea intreaga).
Aceasta reprezentare se numeste reprezentarea cu virgula mobila sau cu virgula flotanta (floating point). Ideea
este relativ simpla: se pastreaza cifrele semnificative ale numarului (toate de la parte intreaga si atatea cate incap
de la partea fractionara) si pozitia virgulei.

Cele trei elemente a caror reprezentare e necesara, in cazul datelor reale, sunt
semnul: 1 bit
exponentul 8 - 16 biti
mantisa (normalizata) 23 – 63 biti

9
S Exponent Mantisa(normalizata)

Relatia (“grosso modo”) dintre cele trei elemente si numarul N reprezentat este

N= (S) bExpoent * Mantisa

(Detalii la laborator!)

Exista si diferente de interpretare a datelor intregi. De exemplu un numar intreg (mai mic decat 255) poate fi
interpretat si ca un caracter care are codul ASCII egal cu valoarea intregului. Invers, orice cod ASCII este un
numar intreg!
Problema consta doar in a putea preciza in cursul unui program modul in care se doreste interpretata o data.

Tipuri de date de baza in C

Fiecare mod de reprezentare a datelor, utilizabile in C, care precizeaza


 plaja de valori
 operatiile permise

se numeste tip de data. Tipurile de baza (sau fundamentale) in C sunt:

 pentru date intregi:


char, short, int, long

 pentru date reale:


float, double, long double

In esenta, tipul unei date specifica:

 Spatiul necesar reprezentarii (1, 2, 4, 8, 10 octeti)


 Modul de reprezentare (complement fata de 2 sau virgula mobila)

Modul in care o data se reprezinta/interpreteaza se precizeaza

 in cazul constantelor literale prin modul de scriere


 iar in cazul variabilelor prin declaratia de tip

Specificarea constantelor

Constantele se pot specifica in doua moduri:


 prin valoarea lor (constante literale)
 prin definirea unor simboluri carora li se asociaza valoarea constantei (constante simbolice)

Definirea constantelor simbolice face uz tot de constante literale pentru a asocia o valoare simbolului ce se
defineste.

10
Specificarea constantelor intregi
In mod implicit constantele intregi se reprezinta ca date de tipul int. In cazul in care valoarea constantei este
in afara plajei de valori pentru acest tip, se va utiliza tipul long.
Exemple:
-32768, -1, 0, 1 1000, 5000, 32767 constante intregi reprezentate ca int
-32769, 32768, 100000 constante intregi reprezentate ca long

Fortarea reprezentarii ca long, chiar daca valoarea sa n-ar justifica-o, se poate face prin utilizarea sufixului L
Exemple:
-1L, 0L, 1L

Fortarea reprezentarii unei constante pozitive ca un intreg fara semn se face prin utilizarea sufixului U
Exemple:
1U, 32768U, 65535U constante intregi reprezentate ca unsigned int
1UL, 50000UL 65536U constante intregi reprezentate ca unsigned long

Constantele intregi pot fi specificate si in baza de numeratie 8, respectiv 16 prin utilizarea prefixelor 0 respectiv
0x sau 0X.
Exemple:
01, 010, 01777 constante octale
0x1, 0XA5, 0XFF, 0xFFFF constante hexazecimale

Specificarea constantelor flotante


In mod implicit, constantele reale se reprezintaca date de tipul double. Semnul distinctiv fata de specificarea
constantelor intregi il reprezinta utilizarea punctului zecimal.
Exemple:
-100., -1.5, .5, 0.75, 1.57 constante reale reprezentate ca double
-1.57F, 3.14F constante reale reprezentate ca float
123456789.987654321L constante reale reprezentate ca long double

Declaratii

Declaratii: - de variabile
- de functii

Terminologie (concepte):

- obiecte
- lvalue
- tipuri (de date)
- declaratii de tip
- clase de memorare
- domenii (scope)
- vizibilitate
- durata
- linkage

11
Definitie. Obiect: o regiune identificabila de memorie care poate pastra o valoare fixa (constanta ) sau
variabila.

OBS. Termenul de obiect definit mai sus nu trebuie confundat cu termenul omonim utilizat in programarea
orientata obiect!

Fiecare obiect are: - un nume


- un tip

Prin nume se acceseaza obiectul (valoarea pastrata in obiect).


Numele este un identificator sau o expresie care indica obiectul. Un identificator poate fi format din
litere (mici sau mari), cifre si _ (underscore). Orice identificator trebuie sa inceapa cu o litera sau cu _.
Utilizarea caracterului _ la inceputul unui identificator trebuie facuta cu grija, intrucat, acest caracter e
utilizat si pentru simbolurile din functiile de biblioteca.
Exemple:

i, contor, a[5], a[i], *p, (p+5i), _Sin

Standardul ANSI C asigura unicitatea identificatorilor care sunt diferiti pe primiele 31 de caractere.

Prin tip:
- se determina spatiul de memorie necesar pt obiectul in cauza
- se precizeaza modul de reprezentare interna

Implicit, informatiile de mai sus determina plaja de valori pe care le poate retine obiectul respectiv.
Tipul unui obiect este utilizat in generarea codului necesar manipularii obiectului respectiv.

In sensul definitiei de mai sus, obiecte sunt variabilele (care nu sunt create in registri!) si functiile.

Lvalue
Un lvalue este un identificator sau o expresie care desemneaza un obiect.
Un lvalue modificabil este un lvalue care identifica un obiect care poate fi accesat si modificat.

Exemplu de lvalue care nu este modificabil: o variabila declarata const.: const float pi=3.14;

O declaratie:
- stabileste legatura intre identificator si obiectul pe care-l identifica.
- asociaza un identificator cu un tip de data

Declaratiile pot fi :
- de definire, care determina crearea unui obiect (alocarea de memorie si posibila sa initializare)
- de referire, care aduce la cunostinta compilatorului existenta si tipul obiectului accesibil prin
identificatorul precizat in declaratie.

OBS. Pot exista mai multe declaratii de referire a unui obiect (mai ales in programe constituite din mai
multe fisiere sursa), dar una singura de definire.

OBS. In general un identificator nu poate fi utilizat (referit) inainte de declarare.


Exceptie: pot fi referite "inainte” (de declaratie) etichetele si functiile nedeclarate.

12
Declaratii de variabile (sintaxa)

Orice variabila trebuie decalarta inainte de prima utilizare.

Sintaxa declaratiilor de variabile este

Tip identificator1[, identificator2[, identificator3 [, ...]]];

Unde, in paranteze drepte sunt mentionate elementele optionale (care pot lipsi).
Tipurile de baza (fundamentale) in C sunt:

char, int , short , long, (tipuri intregi)


float, double, long double (tipuri reale)

Clase de memorare

Asocierea identificatorilor cu obiecte presupune ca fiecare identificator sa aiba cel putin doua atribute
specificate:
- clasa de memorare
- tipul (datelor ce vor fi memorate - in cazul variabilelor, sau al datelor returnate - in cazul functiilor)

Clasa de memorare: specifica unde va fi creat obiectul si durata de viata a obiectului.

Un obiect poate fi creat in:


- zona (segmentul) de date
- registri
- heap
- stiva

Clasa de memorare poate fi determinata de locul de plasare a declaratiei in fisierul sursa sau de utilizarea
modificatorilor de clasa de memorare.

In functie de locul in care apare declaratia in fisierul sursa clasa de memorare a unui obiect

- auto (sau register, daca compilatorul e setat astfel incat sa poata crea variabile registru din proprie
initiativa!) daca declaratia se face in interiorul unei functii (bloc – un bloc este u secventa de
declaratii si instructiuni cuprinsa intre acolade),
- static, daca declaratia este plasata in afara oricarei functii

Prin utilizarea modificatorilor de clasa de memorare, clasa de memorare a unui obiect poate fi:
- auto – obiectele cu aceasta clasa de memorare se creaza pe stiva. Variabilele decalarte in
interiorul unui bloc, numite si variabile locale au aceasta clasa de memorare.
- register (nu este imperativ ci orientaiv!)
- static – obiectele cu aceasta clasa de memorare se creaza in zona de date statice
- extern – aceasta clasa de memorare este tipica pentru obiectele declarate (create intr-un alt
fisier sursa)

13
Utilizarea modificatorului static in declaratia unei variabile globale (sau a unei functii) “ascunde”
variabila/functia respectiva in interiorul fisierului sursa in care apare.

Domeniul unui identificator


Reprezinta partea unui program in care identificatorul poate fi utilizat pentru a accesa obiectul pe care-l
identifica. (ex. cu persoane cu acelasi prenume in familii diferite)

Tipuri de domenii:
- bloc (local)
- functie
- prototip de functie
- fisier (global)

Domeniul unui identificator depinde de locul si modul declararii.

Domeniul unui identificator cu domeniul bloc (local) incepe din locul declaratiei si pana la sfarsitul
blocului in care a fost declarat. (acelasi domeniu il au si parametri din definitia unei functii)

Domeniul functie se refera doar la identificatorii utilizati ca etichete: etichetele pot fi utilizate in
instructiuni goto oriunde in functia in care e declarata eticheta. Etichetele trebuie sa fie unice intr-o functie.

Domeniul prototip de functie este propriu identificatorilor din lista de parametri din prototipul
(declaratia) unei functii si se termina la sfarsitul prototipului functiei.
Ex. Prototip de functie in care se folosesc denumiri de parametri diferite de cele din definitia aceleiasi
functii!

Domeniul fisier (global) este tipic identificatorilor declarati in afara oricarui bloc.

Spatiul unui nume reprezinta domeniul in care un identificator trebuie sa fie unic.

Vizibilitatea
unui identificator reprezinta regiunea dintr-un program sursa de unde se poate accesa obiectul asociat
identificatorului.

Domeniul include vizibilitatea (care poate fi insa mai restransa)

Durata
reprezinta perioada din executia unui program in care un identificator are asociat un obiect fizic (existent in
memorie).

Se poate distinge deasemenea si intre obiecte existente in timpul compilarii (compile-time objects - ex. typedef-
uri si tipuri) sau in timpul executiei (run-time objects- ex. variabile).

Durata poate fi:


- statica (zona de date statica); dureaza pana la sfarsitul executiei
- locala (stiva sau registru); dureaza pana la parasirea domeniul; trebuie initilizate explicit
- dinamica (heap); create/distruse prin apel de functii specifice

14
Prin linkage se precizeaza legatura (asocierea) dintre fiecare instanta (aparitie in programul sursa) a unui
identificator si obiectul care poate fi accesat prin intermediul lui. Un identificator poate avea linkage intern,
extern sau de nici un fel.

Modificatori de declaratii

Anumite atribute ale variabilelor pot fi modificate (fata de valoarea implicita) prin utilizarea modificatorilor de
declaratii. Acestia sunt cuvinte cheie care, utilizate intr-o declaratie modifica valoarea implicita a unui atribut al
variabilelor/functiilor precizate in declaratie.

Modificatorul const
Precizeaza ca valoarea variabila declarate (si initializate!) nu poate fi modificata.

Ex.
const int origin=0; /*corect*/

cons int x=100, y; /*gresit: y trebuie initializat!*/


x=1000; /*gresit: x nu poate fi modificat!*/
y=1000; /*gresit: y nu poate fi modificat!*/

Modificatorii signed si unsigned

Modificatorii signed si unsigned se utilizeaza in declaratiile variabilelor/functiilor intregi pentru a


specifica in mod explicit ca e vorba de intregi cu/fara semn, modificand valoarea implicita a acestui atribut.

Modificatorii de clasa de memorare


Modificatorii de clasa de memorare sunt:

static
auto
register
extern

Initializarea variabilelor pe linia de declaratie

Variabilele pot fi initializate pe linia de declaratie prin utilizarea operatorului de asignare (=). Expresiile care se
folosesc pentru initializare trebuie sa fie de tipul declarat al variabilei care se initializeaza sau sa poata fi convertit
la acest tip. Eventualele variabile care apar in expresii folosite ca initializatori trebuie sa fi fost deja definite!

Declararea si initializarea tablourilor


Sintaxa declaratiei unui tablou este:

TIP nume_tablou[ expr_constanta];

sau

15
TIP nume_tablou[expr_constanta] ={lista initializatori};

OBS lista trebuie sa nu aiba mai multe elemente decat valoarea expr_constanta iar elementele se separa prin
virgula.
In absenta expr_constanta, numarul de elemente ale listei de initalizatori determina numarul de elemente ale
tabloului.

Exemple:
int tab[10], a[5]={-2, 5, 8, 3, 9};
char judet[ ]=”Timis”;

Expresii si operatori
Definitie. Succesiune de identificatori, constante, apeluri de functii (care intorc o valoare) si operatori, care
respecta regulile sintatice.

Expresiile pot fi constante, atunci cand implica doar constante si operatori; astfel de expresii sunt evaluate la
momentul compilarii.

Orice expresie se evalueaza si produce un rezultat. Rezultatul evaluarii unei expresii are un tip care depinde de
tipul operanzilor si de operatorii din expresie.

Evaluarea expresiilor se face in concordanta cu anumite reguli care pot fi sintetizate in urmatoarea formulare:

In absenta parantezelor, evaluarea expresiilor se face in ordinea precedentei operatorilor, iar daca
operatorii au aceeasi precedenta in ordinea data de asociativitatea operatorilor (de regula de la stanga la
dreapta).

Operanzi pot fi orice valori ( constante, variabile, valori returnate de functii, valori rezultate in urma evaluarii
unei expresii) care se conformeaza tipului de opranzi asteptati de operatorii in cauza. (Contra)exemple: nu pot
figura ca operanzi intr-o expresie aritmetica structuri/uniuni/campuri de biti (in ansamblu); valori reale nu pot fi
operanzi in expresii cu operatorul %, operatorii logici pe biti, operatorii de deplasare.

In scopul evaluarii expresiilor operanzii simpli (care, la randul lor, nu sunt expresii) trebuie sa fie evaluati pentru
a li se obtine valoarea. Ordinea in care se face evaluarea operanzilor simpli nu este precizata de standardul tiple
ale aceluiasi operand in situatia in care cel putin una din aparitii este in contextul unei opratii cu efecte secundare.
Exemplu:

a[i++]=i;

16
Operatori

Tabela cu ordinea de precedenta a operatorilor limbajului C (preluata din B.Kernigham, D. Ritchie, - The C
Programming Language, 2nd ed.) este prezentrata mai jos. Precedenta scade de la prima linie pana la ultima. In
aceeasi linie sunt plasati operatori cu aceeasi precedenta.

OPERATORS ASSOCIATIVITY
( ) [ ] --> . left to right
! ~ ++ -- + - * & (type) sizeof right to left
* / % left to right
+ - left to right
<< >> left to right
< <= > >= left to right
== != left to right
& left to right
^ left to right
| left to right
&& left to right
|| left to right
?: right to left
= += -= *= /= %= &= ^= |= <<= >>= right to left
, left to right

Operatorii disponibili in limbajul C pot fi clasificati in urmatoarele categorii:

Operatori aritmetici
+ adunare
- scadere
* inmultire
/ impartire
% rest modulo

Operatori de incrementare/decrementare

++ operatorul de incrementare
-- operatorul de decrementare

Efectul operatorilor de incrementare/decrementare este

i++ (sau ++i) este echivalent cu i=i+1


i-- (sau --i) este echivalent cu i=i-1

17
OBS. Cei doi operatori de mai sus sunt operatori unari (presupun un singur operand) si au particularitatea ca pot
fi folositi ca operatori prefixati sau postfixati. Pozitia lor in raport cu operatorul indica momentul in care se face
evaluarea: inainte, respectiv dupa de evaluarea operandului, evaluare care se face in eventualitatea ce operandul
respectiv face parte dintr-o expresie in care intervin si alti operatori (pe langa cei de incrementare/ decrementare

Ex. In expresia
a[i++] + b[++j]
se ia elementul de indice i din tabloul a si apoi se incrementeaza I, respectiv se incrementeaza j si apoi se ia
elementul corespunzator acestei noi valori a lui j din tabloul b pt evaluarea expresiei.

Operatori relationali

==
!=
<
>
<=
>=

Operatori logici

&& - “si “ logic


|| - “ sau” logic
! - “not” (negatia)

Evaluarea expresiilor relationale si logice produc intotdeauna valoarea 0 sau 1, dupa


cum expresia este falsa sau adevarata.

In functie de valoarea operanzilor, operatorii logici “produc” urmatoarele rezultate:

&& | 0 0 || | 0 0
0 | 0 0 0 | 0 1
0 | 0 1 0 | 1 1

! | 0 0
| 1 0

Operatori pe biti
Operatorii din aceasta categorie actioneaza asupra bitilor de pe aceasi pozitie din operanzi.
& - “si” pe biti
| - ”sau” pe biti
^ - “sau exclusiv” pe biti
~ - complementare

18
Tinand cont ca operatorii din aceasta categorie actioneaza de fapt asupra bitilor din operanzi, in descrierea
regulilor de evaloare, operanzii vor fi valorile posibile pentru un bit, adica 0 si respectiv 1:

& | 0 1 | | 0 1 ^ | 0 1
0 | 0 0 0 | 0 1 0 | 0 1
1 | 0 1 1 | 1 1 1 | 1 0

~ | 0 1
| 1 0

OBS. Operatorii pe biti se folosesc de obicei pentru “stergerea” (punerea pe 0) sau “setarea” unor biti. De obicei,
unul din operanzi e considerat masca prin care se precizeaza pozitiile din celalalt operand care trebuie sterse sau
setate. Operatorul & se utilizeaza pentru stergerea bitilor care in masca au valoarea 0 (si pastrarea neschimbata a
celor care au valoarea 1 in masca!) iar operatorul | se utilizeaza pentru setarea pe 1 a bitilor care in masca au
valoarea 1 (si pastrarea nemodificata a celor care in masca au valoarea 0!)

Operatori de deplasare

>> - deplasare spre dreapta


<< - deplasare spre stanga

Sintaxa:
operand_1 >> nr_pozitii
operand_1 << nr_pozitii

Al doilea operand precizeaza numarul de pozitii cu care se deplaseaza spre dreapta sau spre stanga bitii primului
operand. Ambii operanzi trebuie sa fie de tip intreg.
Indiferent de tipul operandului_1 (cu sau fara semn) pozitiile eliberate in cazul deplasarii la stanga (cele mai
putin semnificative!) se completeaza cu 0.
Pozitiile eliberate in cazul deplasarii la dreapta (cele mai semnificative!) se completeaza in functie de tipul
operandului_1 (cu sau fara semn)
- cu bitul cel mai semnificativ, daca operandul e cu semn
- cu 0 , daca operandul este fara semn (unsigned)

Operatorul conditional

? :

Sintaxa:

exp1 ? exp2 : exp3

Mod de evaluare: daca exp1 este adevarata atunci rezultatul intregii expresii este rezultatul produs de evaluarea
expresiei exp2, altfel cel produs de evaluarea lui exp3.

19
Operatori de asignare

= operatorul de asignare
op= operatorul de asignare compus. Efectul acestui operator este descris de

v op= u; este echivalent cu v = v op u;

Conversii de tip

Conversiile (in sensul discutiei care urmeaza) reprezinta transformari dintr-un mod de reprezentare a datelor in
altul. Doua tipuri de conversii vor fi abordate:

- a) conversii din reprezentarea interna in reprezentarea externa (lizibila) si invers


- b) conversii intre diverse modalitati de reprezentare interna (conversii de tip)

Conversii din reprezentarea interna in reprezentarea externa si invers


Intern (pentru stocarea in memorie si efectuarea de operatii) datele sunt reprezentate intr-unul din urmatoarele
doua modalitati:

- in complement fata de doi (date intregi)


- in virgula mobila (date reale)

Extern ( atunci cand sunt generate de dispozitive de intrare cu care utilizatorul interactioneaza direct - tastatura -
respectiv, pentru afisarea pe dispozitive de iesire destinate uzului utilizatorului uman - display, imprimanta)
datele sunt reprezentate ca succesiuni de coduri (de ex. ASCII), cate un cod pentru fiecare caracter (cifra, litera,
semn de punctuatie, etc).
Cand se citesc date de la tastatura, un program primeste o succesiune de coduri (de ex. ASCII) reprezentand

- numere
- intregi (ex. -1, 123, 1000) sau
- reale (ex. 0.5, -1.0, 3.14)
- "text" (orice altceva decat numere)
Datele numerice trebuie transformate in modul de reprezentare intern pentru a se putea efectua operatii cu ele.
Datele de tip text se pastreaza de obicei ca siruri de caractere (coduri ale caracterelor care compun textul)

Conversiile de date numerice in modul de reprezentare interna se realizeaza dupa regula generica:

c0 c1 ...c n conversie
  c n  b  (c n 1  ...b  (c1  b  c0 )...)
Unde c0,c1,…cn sunt cifrele numarului intreg de convertit din reprezentarea externa in reprezentarea interna iar b
este baza de numeratie in care se reprezinta extern numarul (de obicei b=10).

Pentrut numerele reale, conversia se face conform regulii:

(c n  b  (c n 1  ...b  (c m 1  b  c m )...))  b  m


c0 c1 ...c n .c n 1c n  2 ...c n  m conversie


Unde m reprezinta numarul de cifre de la partea fractionara.


Pentru conversia din reprezentarea interna in cea externa, trebuie obtinute cifrele numarului respectiv, lucru
posibil utilizand restul modulo b (restul imartirii intregi a numarului la baza de numeratie) pentru cea mai putin
semnificativa cifra, apoi restul modulo b fata de catul dintre numarul initial si baza de numeratie b, pentru a doua

20
(cea mai putin semnificativa) cifra si asa mai departe, pana cand catul impartirii devine mai mic decat b. Evident,
intrucat cifrele se obtin si se acumuleaza intr-un buffer in ordine inversa, este necesara o inversare a sirului de
caractere astfel obtinut.

Mai jos se dau codurile sursa pentru cateva rutine de conversie (din B.Kernigham & D.Ritchie - The C
Programming Language, 2nd edition - [K&R 88] ).

/* atoi: convert s to integer */


int atoi(char s[ ])
{
int i, n;

n = 0;
for (i = 0; s[i] >= '0' && s[i] <= '9'; ++i)
n = 10 * n + (s[i] - '0');
return(n);
}

#include <ctype.h>

/* atof: convert string s to double */


double atof(char s[ ])
{
double val, power;
int i, sign;

for (i = 0; isspace(s[i]); i++) /*skip white space */


;
sign = (s[i] == '-') ? -1 : 1;
if (s[i] == '+' || s[i] == '-')
i++;
for (val = 0.0; isdigit(s[i]); i++)
val = 10.0 * val + (s[i]- '0');
if (s[i] == '.')
i++;
for (power = 1.0; isdigit(s[i]); i++) {
val = 10.0 * val + (s[i] -'0');
power *= 10.0;
}
return sign * val / power;
}

/* atoi: convert string s to integer using atof */


int atoi(char s[ ])
{

double atof(char s[ ]);

return (int) atof(s);


}

21
/* itoa: convert n to characters in s */
void itoa(int n, char s[ ])
{
int i, sign;

if ((sign = n) < 0) /* record sign */


n = -n; /* make n positive */
i = 0;
do { /* generate digits in reverse order */
s[i++] = n % 10 + '0'; /* get next digit */
} while ((n /= 10) > 0); /* delete it */
if (sign < 0)
s[i++] = '-';
s[i] = '\0';
reverse(s);
}

Cand conversiile se fac intre diverse modalitati de reprezentare interna, se vorbeste de conversii de tip.
Conversiile de tip pot avea loc implicit sau explicit.

Conversii implicite de tip

Atunci cand operanzii unei expresii sunt de tipuri diferite, valoarea operandului de tip “mai slab” se
converteste la tipul operandului de tip “mai tare”.

OBS. De convertit se converteste valoarea, deci, in cazul unui operand -variabila, tipul variabilei nu se schimba!

Prin tip mai tare vom intelege in continuare un tip care permite reprezentarea a mai multa informatie (plaja de
valori mai mare si/sau parte fractionara) decat un tip mai slab.
De exemplu tipul int este mai tare decat tipul char pentru ca plaja de valori reprezentabile este mai mare
(-32768 pana la 32767 fata de -128 pana la 127), tipul float este mai tare decat tipul long int pentru ca are
plaja de valori reprezentabile mai mare si in plus permite si reprezentarea partii fractionare.

Pe scurt regulile dupa care au loc conversiile implicite pentru operanzi fara semn sunt urmatoarele:

Daca unul din operanzi este long double, atunci si celalalt este convertit la long double.
altfel
daca unul din operanzi este double atunci si celalalt este convertit la double
altfel
daca unul din operanzi este float atunci si celalalt este convertit la float
altfel
se converteste operandul (operanzii) char si short la int
apoi
daca unul din operanzi este long int, atunci si celalalt operand se converteste la long int.

Daca unul din operanzi este unsigned , lucrurile se complica din cauza comparatiilor dintre valorile cu semn
si cele fara semn, care sunt dependente de implementare, intrucat depind de lungimea reprezentarii pentru tipurile
intregi.

Exista o exceptie de la regula enuntata anterior, privind conversiile implicite si anume, cand operatorul este
operatorul de asignare, atunci operandul din partea dreapta se converteste la tipul operandului din partea stanga,

22
chiar daca acest lucru presupune pierdere de informatie, intrucat in urma asignarii, valoarea operandului drept
trebuie stocata in zona de memorie ocupata de operandul stang, zona care este fixata (prin declaratia de tip!).
La conversia unei valori de tip intreg mai tare la un tip intreg mai slab se trunchiaza cifrele cele mai
semnificative. In cazul conversiei unei valori reale la un tip intreg se trunchiaza partea fractionara si, daca e
cazul, cifrele cele mai semnificative care nu pot fi reprezentate.
In cazul conversiei unei valori reale de tip mai tare la un tip real mai slab se face sau nu rotunjire, in functie de
implementare.

Exemple

Intrucat argumentele functiilor sunt la randul lor expresii, conversiile de tip au loc si atunci cand argumentul
actual e diferit de tipul specificat in prototipul (declaratia) functiei. In absenta prototipului unei functii
argumentele actuale de tip char si short se convertesc la double. De aceea parametrii functiilor care in mod
normal ar fi de tipul char/short se vor declara de tipul int (pentru a se economisi conversia implicita la int).

Conversii explicite de tip

Conversiile explicite de tip se realizeaza cu ajutorul operatorului de cast.

(tip)expresie

23
Controlul executiei (instructiuni)
Limbajul C este un limbaj “mic”, cu putine instructiuni. Aceasta caracteristica permite pe de o parte o
standardizare mai usoara cu efect direct asupra usurintei de implementare Instructiunile limbajului C sunt:

 Instructiuni de test: if-else si else-if


 Instructiunea de selectie: switch
 Instructiunile de ciclare: for, while si do-while
 Instructiunile de control a ciclurilor: break si continue
 Instructiunea de revenire din functia apelata: return
 Instructiunea de salt neconditionat: goto

Instructiuni de test

If-else

Sintaxa:

if(expresie)
instructtiune_1;
else
instructiune_2;

Executie:
Daca expresie este adevarata (valoarea numerica este diferita de 0) se executa instructiune_1, altfel se
executa instructiune_2.

Ramura else poate lipsi.


Ex.
if(n<0)
n=-n;

In cazul in care instructiune_1 sau instructiune_2 sunt la randul lor if-uri ramura else se asociaza celui mai
apropiat if care o precede. Se poate controla (forta) asocierea ramurii else cu un anumit if prin utilizarea
acoladelor.

Ex.
1.
if(n>=0)
if(a>b)
z=a;
else
z=b;
else
printf(" n - negativ");

2.
if(n>=0) {
if(a>b)
z=a;

24
}
else
z=b;

Else-if

Sintaxa:

if(expresie_1)
instructiune_1;
else if(expresie_2)
instructiune_2;

else if(expresie_n)
instructiune_n;
else
instructiune;

Executie:

Daca expresie_1 este adevarata se executa instructiune_1, altfel, daca expresie_2 este adevarata se
executa instructiune_2, altfel,…, daca expresie_n este adevarata se executa instructiune_n altfel
se executa instructiune. Altfel spus, instructiunea else-if permite o selectie din mai multe variante
posibile (cea corespunzatoare primei expresii adevarate. Deasemenea, instructiunea else-if ofera si o varianta
implicita (ramura else instructie ), pentru cazul cand niciuna din expresiile expresie_i nu este
adevarata.

Ex.
if(a>0)
printf("a este pozitiv");
else if(a<0)
printf("a este negativ");
else
printf("a=0");

Instructiunea de selectie

Switch

Sintaxa:

switch(expresie){
case expresie_constanta_1: instructiune_1;
break;
case expresie_constanta_2: instructiune_2;
break;

case expresie_constanta_n: instructiune_n;


break;
default: instructiune;

25
Executie:
Se va executa instructiunea asociata cu expresie_constanta_i care are aceeasi valoare cu cea a
expresiei expresie (din switch). In cazul in care valoarea expresiei expresie nu coincide cu a nici uneia
din expresiile expresie_constanta_I se va executa instructiune (cazul implicit default). In cazul
in care secventa de instructiuni asociata cazului selectat nu se termina cu break se vor executa in continuare toate
instructiunile, pana la primul break.

Exemple:

Instructiuni de ciclare

For

Sintaxa:

for(expresie_1; expresie_2; expresie_3)


instructiune;

Executie:
1. Se eevaluaeaza expresie_1
2. Se evalueaza expresie_2 si
 Daca este falsa se paraseste ciclul for
 Daca este adevarata se executa instructiune
3. Se evalueaza expresie_3
4. Se reia pasul 2

OBS. Daca expresie este falsa la prima evaluare, ciclul nu se executa niciodata!

Exemple:

for(i = 0; i<n ; i = i +1) /*ciclu tipic de initializare a elementelor unui tablou */


a[i] = 0; /*cu N elemente*/

for( ; ; ){ /* ciclu infinit, intrerupt prin break la indeplinirea unei */


if((c=getchar()) !=EOF) /* conditii */
break;

}

While

Sintaxa:
while (expresie)
instructiune;

Executie:

Se evalueaza expresie si daca este adevarata se executa instructiune dupa care se revine la
evaluarea expresiei. Cand expresie devine falsa, se paraseste ciclul.

26
OBS. Daca expresie este falsa la prima evaluare, ciclul nu se executa niciodata!

Ex.
while((c=getchar( )) != EOF){ /* ciclu tipic de citire de la stdin (tastatura) */

}

while( 1 ) { /* ciclu infinit, din care se poate iesi fortat (cu break sau return)*/

}

Do-while

Sintaxa:
do {
instructiune;
} while(expresie);

Executie:
Se executa instructiune, apoi se evalueaza expresie si daca este adevarata se executa din nou
instructiune… Cand expresie devine falsa, se paraseste ciclul.
OBS. Ciclul se executa cel putin odata.

Exemplu:

Instructiuni de control a ciclurilor


Break

Sintaxa:
break;

Executie:
Instructiunea se utilizeaza in contextul unei instructiuni de ciclare sau al unui switch si determina
parasirea fortata a acestuia.
Ex.

while((c=getchar( )) != ‘ ‘ && c != ‘\t’ && c != ‘\n’){


if(c==EOF)
break;

}

Continue

Sintaxa:
continue;
Executie:
Instructiunea se utilizeaza in contextul unei instructiuni de ciclare (nu si al unui switch!) si determina
trecerea la iteratia urmatoare.

27
Ex.
for( i=0; i<n ; i++){
if(t[i] < 0) continue;

}

Instructiunea de revenire din apel de functie


Return

Sintaxa:
return;
sau
return expresie;
Executie:
Instructiunea return transfera controlul executiei din functia apelata inapoi in functia apelanta, la
adresa de revenire salvata pe stiva, in contextul de apel. Daca tipul declarat al functiei este diferit de void,
atunci se utilizeaza forma a doua si expresie trebuie sa fie de tipul declarat al functiei sau convertibil la acesta.
In absenta unei instructiuni return, ultima acolada inchisa din definitia functiei joaca rolul instrucitunii return.

Ex.

Instructiunea de salt neconditionata


Goto

Sintaxa:
goto eticheta;
Executie:
Controlul executiei se transfera la instructiunea (din aceeasi functie!) precedata de eticheta.
Eticheta este un identificator cu domeniul intreaga functie in care e definita si poate fi formata din caractere
alfanumerice. Definirea unei etichete se face ca mai jos:
eticheta:

Utilitatea instructiunii goto se manifesta in cazul in care se doreste parasirea mai multor cicluri imbricate intr-un
singur pas. {Cu instructiunea break se poate parasi doar ciclul in care se foloseste instructiunea!)

Ex.

for(…)
for(…)
for(…){

if(expr)
goto error;
}


error:

28
Structura programelor. Functii.
Filozofia (imprumutata din UNIX si) promovata de autorii C-ului ca si de standardul limbajului prevede
descompunerea sarcinilor de realizat de catre un program in unitati/module functionale numite functii. Avantajele
care deriva din aceasta filozofie se reflecta in
 posibilitatea crescuta de refolosire a codului si
 intr-o mai mare usurinta a modificarii/intretinerii codului aferent unei anumite sarcini.

Intr-un program C se pot distinge trei contexte/situatii in care se face referire la o functie:

 declaratia functiei
 definitia functiei
 apelurile (apelarile) functiei

Declaratia unei functii se mai numeste si prototip al functiei.


Primul apel al oricarei functii ar trebui precedat de declaratia functiei. Standardul C nu impune acest lucru (spre
deosebire de standardul pentru C++) dar in absenta unui prototip, primul apel al functiei este considerat ca
declaratie din care se deduce tipul functiei ( in abasenta prototipului, tipul functiei este considerat a fi int). In
ceea ce priveste argumentele, despre tipul si numarul acestora nu se face (compilatorul nu face!) nici o
presupunere si se transmit functiei apelate asa cum sunt citate in apel.

Declaratii de functii
Sintaxa unei declaratii de functie este:

TIP nume_functie (TIPARGi );

Unde
 TIP este tipul valorii pe care functia o intoarce/returneaza in urma unui apel.
 Nume_functie este numele sub care se vor face referiri (apeluri) la functie.
 In paranteza se precizeaza lista tipurilor argumentelor functiei.

OBS. 1. Daca tipul declarat al functiei (TIP) este void, aceasta inseamna ca functia nu intoarce nici o valoare.
2. Lista de argumente poate fi vida, ceea ce inseamna ca la apelul functiei nu i se trimite nici un
argument.
3. Nu este necesara (dar nici interzisa!) precizarea, in declaratie, de nume pentru argumentele functiei.

In definitia functiei:
 Se precizeaza numele argumentelor formale
 Se declara variabilele locale
 Se descrie (printr-o secventa de instructiuni) actiunile de indeplinit la fiecare apel al functiei.
 Se citeaza (intr-o instructiune return expresie) valoarea pe care functia o returneaza functiei
apelante (numai daca tipul declarat al functiei este diferit de void)

Sintaxa unei definitii de functie:

TIP nume_functie(TIPARGi nume_argi)


{
/*declaratii de variabile locale si functii*/

29
/* instructiuni */

return expresie; /* expresie trebuie sa fie de tipul declarat */


/*al functiei, adica TIP*/
}

Daca TIP este diferit de void, atunci ar trebui sa apara si o instructiune return in definitia functiei.

OBS. Definitia unei functii precizeaza CE SE FACE cu ARGUMENTELE primite la apel in scopul
obtinerii VALORII ce se va RETURNA functiei apelante.
Intr-un program poate apare o singura definitie pentru fiecare functie.

Apelurile unei functii reprezinta mecanismul prin care controlul executiei este transferat unei functii,
impreuna cu argumentele actuale, in scopul obtinerii unui anumit rezultat si/sau al efectuarii unor
anumite actiuni.
Sintaxa unui apel de functie:

nume_functie(argument_actuali);/* apel de functie care nu intoarce nimic (are tipul void) */

var=nume_functie(argument_actuali);/* apel de functie care intoarce o valoare (are alt */


/* tip decat void)*/

Deasemenea apelul unei functii care intoarce o valoare poate apare in orice tip de expresii, inclusiv ca
argument in apelul altei functii. Exemplu:

putchar(getchar( ));

Transmiterea argumentelor. Contextul de apel. Conventia de apel C

Argumentele se transmit de la functia apelanta la functia apelata prin intermediul stivei. Tot pe stiva se depune
adresa de revenire din apel, adica adresa instructiunii la care se transfera controlul executiei dupa ce s-a
terminat de executat functia apelata.
Argumentele se transmit prin valoare in ordinea inversa celei din lista de argumente, astfel incat primul
argument din lista sa fie intotdeauna sub adresa de revenire. Daca functia apelata are si variabile locale, acestea
se creaza tot pe stiva in ordinea declararii. Zona de pe stiva pe care se plaseaza valoarea argumentelor actuale,
adresa de revenire si variabilele locale poarta denumirea de context de apel.
Acest mod (ordine) de transmitere a argumentelor catre functia apelata poarta denumirea de conventie de apel C.

Comunicarea intre functii

In programele C, functiile pot comunica prin:

 Argumentele transmise de functia apelata functiei apelante


 Valoarea returnata de functia apelata functiei apelante
 Variabile globale.

Exemplu de comunicare prin variabile globale: program de evaluare a expresiilor aritmetice (+, -, *, /) introduse
in notatie poloneza inversa (din [K&R], 4.3)

30
Textual, sarcina de indeplinit poate fi descrisa de urmatoarea secventa:

while (next operator or operand is not end-of-file indicator)


if (number)
push it
else if (operator)
pop operands
do operation
push result
else if (newline)
pop and print top of stack
else
error

#include <stdio.h>
#include <math.h> /* for atof() */

# define MAXOP 100 /* max size of operand or operator */


# define NUMBER '0' /* signal that a number was found */

int getop(char []);


void push(double);
double pop(void);

/* reverse Polish calculator */


main()
{
int type;
double op2;
char s[MAXOP];

while ((type = getop(s)) != EOF) {


switch (type) {
case NUMBER:
push (atof(s));
break;
case '+':
push(pop() + pop ());
break;
case '*' :
push (pop() * pop());
break;
case '-':
op2 = pop();
push(pop() - op2);
break;
case '/';
op2 = pop();
if (op2 != 0.0)
push(pop() / op2);
else
printf("error: zero divisor\n");
break;
case 'n';
printf("\t%.8g\n", pop());

31
break;
default;
printf("error: unknown command %s\n", s);
break;
}
}
return 0;
}

OBS. Deoarece operatorii de adunare (+) si inmultire (*) sunt comutativi ordinea in care se “coboara” operanzii
de pe stiva este irelevanta, in schimb, in cazul operatorilor de scadere (-) si impartire (/), care nu sunt comutativi,
in varful stivei se afla operandul al doilea (scazatorul, respectiv impartitorul) care trebuie “coborate” intr-o
variabila temporara (op2) pentru ca apoi ordinea operanzilor in expresie sa fie corecta (pop() – op2, respectiv
pop() / op2).

#define MAXVAL 100 /* maximum depth of val stack */

int sp = 0; /* next free stack position */


double val[MAXVAL]; /* value stack */

/* push: push f onto value stack */


void push(double f)
{
if (sp <MAXVAL)
val[sp++] =f;
else {
printf("error: stack full, can't push %g\n", f);
}

/* pop : pop and return top value from stack */


double pop(void)
{
if (sp > 0)
return val[--sp];
else {
printf("error: stack empty\n");
return 0.0;
}
}

#include <ctype.h>

int getch(void);
void ungetch(int);

/* getop: get next operator or numeric operand */


int getop(char s[])
{
int i, c;
while ((s[0] = c = getch()) == ' ' || c == '\t')
;
s[1] = '\0';
if (!isdigit(c) && c != '.')
return c; /* not a number */
i = 0;
if (isdigit(c)) /* collect integer part */
while (isdigit(s[++i] = c = getch()))

32
;
if (c == '.') /* collect fraction part */
while (isdigit(s[++i] = c = getch()))
;
s[i] = '\0';
if (c != EOF)
ungetch(c);
return NUMBER;
}

#define BUFSIZE 100

char buf[BUFSIZE]; /* buffer for ungetch */


int bufp = 0; /* next free position in buf */

int getch(void) /* get a (possibly pushed back) character */


{
return (bufp > 0) ? buf[--bufp] : getchar();
}

void ungetch(int c) /* push character back on input */


{
if (bufp >= BUFSIZE)
printf("ungetch: too many characters\n");
else
buf[bufp++] = c;
}

33
Gestionarea aplicatiilor complexe. Proiecte

In scopul dezvoltarii si al gestionarii ulterioare a modificarilor in aplicatii complexe, de dimensiuni care nu fac
rezonabila cantonarea tuturor functiilor intr-un singur fisier sursa, parti ale aplicatiei cu functionalitate distincta
se pastreaza in fisiere sursa separate. Aceste fisiere sursa separate se pot modifica si compila independent fiind
“legate” cu fisierele cod obiect rezultat din compilarea celorlalte componente in faza de link-editare. Ansamblul
de fisiere sursa care implementeaza functionalitatea unei aplicatii poarta, in majoritatea mediilor de dezvoltare,
denumirerea de proiect.

Exemplificare: programul anterior, care implementeaza un evaluator pentru expresii introduse de la stdin in
notatia poloneza inversa poate fi realizat intr-o versiune in care diversele functionalitati sunt implementate in cate
un fisier sursa separat. Pentru directivele de preprocesare si declaratiile necesare in mai multe fisiere sursa se
creaza un fisier header care va fi inclus in fisierele sursa care au nevoie de continutul sau. (Exemplul este preluat
din [K&R], 4.5).

calc.h:
----------------------
| #define NUMBER '0' |
| void push(double); |
| double pop(void); |
| int getop(char []);|
| int getch(void); |
| void ungetch(int); |
----------------------

main.c: getop.c: stack.c:


--------------------- -------------------- ----------------------
| #include <stdio.h> || #include <stdio.h> || #include <stdio.h> |
| #include <math.h> || #include <ctype.c> || #include "calc.h" |
| #include "calc.h" || #include "calc.h" || #define MAXVAL 100 |
| #define MAXOP 100 || getop() { || int sp = 0; |
| main() { || ... || double val[MAXVAL];|
| ... || } || void push(double) {|
| } |----------------------| ... |
---------------------- | } |
getch.c: | double pop(void) { |
---------------------- | ... |
| #include <stdio.h> | | } |
| #define BUFSIZE 100| ----------------------
| char buf[BUFSIZE]; |
| int bufp = 0; |
| int getch(void) { |
| ... |
| } |
| void ungetch(int) {|
| ... |
| } |
----------------------

34
Initializarea variabilelor

Atribuirea unei valori unei variabile se realizeaza cu ajutorul operatorului de asignare ( = ). Aceasta atribuire
poate avea loc si in declaratia variabilei. In continuare vom face distinctie intre atribuirea unei valori in linia de
decalaratie a unei variabile, pe care o vom numi initializare, si atribuirea intr-o expresie ulterioara declaratiei pe
care o vom desemna ca asignare (sau atribuire) a unei valori la variabila respectiva. In cazul initializarii vom
face distinctie intre initializarea automata (sau implicita) si initializarea explicita.

Initializarea automata (implicita ) se aplica variabilelor globale si variabilelor locale declarate static. Aceste
variabile, care au ca punct comun faptul ca au clasa de memorare static, se initializeaza in mod automat cu 0.

Variabilele cu clasa de memorare auto nu se initializeaza automat, valoarea lor intiala (in absenta initializareii
explicite) fiind valoarea prezenta, la momentul crearii variabilei, in zona de memorie (de pe stiva) unde aceasta e
creata. Evident aceasta valoare scapa controlului programatorului!

int val; /* variabila globala, initializata automat cu 0 */

f( ) /* definitia unei functii oarecare */


{
static int j; /* variabila statica initializata automat cu 0 */
int k=0; /*initializare explicita*/
k=1; /* asignare */

}

Initializarea explicita
Sintaxa pentru initializarea variabilelor scalare este:

TIP nume_var = expresie;

In cazul variabilelor globale, expresie trebuie sa fie o expresie constanta, pe cand in cazul variabilelor locale
poate contine si variabile deja definite precum si parametri de apel ai functiei in care variabila este definita sau
chiar apeluri de functii.
Ex.
int val = 1;
int contor=val; /*gresit: initializatorul nu este o expresie constanta */

f(int n)
{
int a=2, x=2+n, p=pow(a,n);

}

Initializarea tablourilor

Ca si variabilele scalare, tablourile pot fi initializate. Elementele tablourilor declarate ca variabile globale sau ca
variabile locale statice, se initializeaza implicit cu 0.

35
Initializarea explicita a tablourilor se realizeaza prin utilizarea unei liste de initializatori. Lista este specificata
intre acolade, iar initializatorii sunt separati cu virgula.

Ex.
int a[5] = {15, 4, 3, 9, 21};
float b[ ]={1.5, -2.3, 5.0}; /*tablou dimensionat prin numarul initializatorilor la 3 elemente!*/
int c[3] = {1, 2, 3, 4}; /* greseala: prea multi initializatori!*/
int d[10] ={1, 2}; /* elementele neinitializate explicit se initializeaza implicit cu 0 */

Tablourile de caractere suporta doua modalitati de initializare. Una, comuna tuturor tablourilor:

char oras[30] = {‘B’, ‘R’, ‘A’, ‘S; ‘O’, ‘V’};

OBS. Daca se doreste utilizarea continutului tabloului oras ca sir de caractere, atunci trebuie plasat si
terminatorul de sir (‘\0’):

char oras[30] = {‘B’, ‘R’, ‘A’, ‘S; ‘O’, ‘V’, ‘\0’};

In cazul in care valoarea unui tablou va fi utilizata ca sir de caractere, se initializeaza tabloul cu o constanta sir de
caractere:

char oras[30]=”BRASOV”;

Functii recursive

Functiile C se pot apela pe ele insele, cu alte cuvinte, in C se pot defini functii recursive.
Orice functie recursiva trebuie sa contina o conditie de oprire a apelului recursiv. In absenta acesteia, sau daca
aceasta nu e corecta, apelul recursiv va continua ducand la una din urmatoarele situatii:

a) daca programul a fost compilat cu o optiune care sa includa cod pentru verificarea depasirii stivei,
atunci, cand prin executia repetata a apelului recursiv contextele de apel succesive ajung sa epuizeze
spatiul alocat pentru stiva, programul se termina fortat cu un mesaj de eroare: “Stack overflow”.
b) Daca programul a fost compilat fara optiunea de mai sus, atunci apelul recursiv continua, cu crearea
contextelor de apel dincolo de limita zonei alocata pentru stiva distrugand eventualele date stocate in
zonele respective. In acest caz:
 Daca datele distruse nu sunt critice, apelul recursiv va continua pana la inceputul segmentului de
date dupa care SP-ul (stack pointerul) vas indica din nou adresa de baza a stivei si procesul se
reia…
 Daca datele distruse sunt critice (influenteaza executia in continuare a programului) programul se
termina anormal sau, mai frecvent, se blocheaza masina.

Exemple de functii recursive:

long fact(unsigned int n) /* functie recursiva pentru calculul factorialului*/


{
if( n<= 1)
return 1;
else
return n*fact(n-1);
}

36
void printd(int n) /* printd: afiseaza n ca numar in baza 10 */
{
if (n < 0) {
putchar ('-');
n = -n;
}
if (n / 10)
printd(n / 10);
putchar(n % 10 + '0');
}

void qsort(int v[], int left, int right) /* qsort: sorteaza crescator v[left],…,v[right] */

{
int i, last;
void swap(int v[], int i, int j);

if (left >= right) /* do nothing if array contains */


return; /* fewer than two elements */
swap(v, left, (left + right)/2; /* move partition elem *
last = left; /* to v[0] */
for (i = left+1; i <= right; i++) /* partition */
if (v[i] < v[left])
swap(v, ++last, i);
swap(v, left, last); /* restore partition elem */
qsort(v, left, last-1);
qsort(v, last+1, right);
}

Functiile recursive sunt mai costisitoare (ca timp de executie si ca spatiu de memorie necesar) decat variantele
iterative ale acelorasi algoritmi. Acest dezavantaj provine din necesitatea crearii cate unui context de apel la
fiecare apel recursiv al functiei. Contextele de apel sunt prezente simultan in memorie – de unde consumul de
memorie – iar pe de alta parte, crearea contextului de apel, care implica deplasarea de date inspre stiva este
costisitoare ca timp.
Avantajul variantelor recursive ale unor algoritmi fata de variantele iterative rezida in naturaletea exprimarii,
concizia si chiar eleganta lor.

Preprocesorul C

Preprocesorul C este (in standardul ANSI C) componenta initiala a compilatorului care are sarcina de a
transforma codul sursa, conform unor directive de precompilare, inainte de compilarea prorpiu-zisa.
Directivele de precompilare se specifica pe linii separate care incep cu caracterlu #. Exemple de directive de
precompilare:

Directiva de includere fisiere (header)


#include <nume_fisier_header.extensie>
sau
#include “nume_fisier_header.extensie”

37
Fisierele citate (fisiere text, continand de obicei macrodefinitii, prototipuri de functii, mai rar definitii de functii)
se include in fisierul sursa in locul directivei inainte de si apoi noul fisier sursa rezultat este din nou supus
preprocesarii in vederea expandarii macrodefinitiilor.

Macrodefinitii: definitie si expandare


Macrodefinitiile reprezinta o modalitate de substituire a unui sir de caractere cu un altul. Sintaxa generala este:

#define NUMEMACRO(argi) “Sir de substitutie”

OBS. Paranteza deschisa trebuie sa urmeze imediat (fara spatiu) dupa numele macroului!

Orice aparitie a NUMEMACRO(argactuali) in textul sursa va fi inlocuit cu sirul de substituie in care, in plus,
fiecare aparitie a lui argi este inlocuita cu argactuali. Procesul acesta de substitutie poarta denumirea de
expandare a macroului.

Ex.
#define PI 3.14159
#define TRUE 1
#define BEGIN {
dar si
#define max(A, B) ((A) > (B) ? (A) : (B))
#define square(x) (x)* (x)

Macrodefinitiile ofera o alternativa la definirea de functii, uneori utila prin economia de timp utilizata dar care
trebuie utilizata cu grija datorita posibilelor efecte colaterale. De exemplu , macroul

#define square(x) x * x /* gresit */

“apelat”
square(n+1);
se expandeaza ca
n+1*n+1;
ceea ce e cu totul altceva decat ceea ce se dorea, adica

(n+1)*(n+1);

Solutie: argumentele se plaseaza intre paranteze atunci cand sunt citate in sirul de substitutie.

#define square(x) (x) * (x) /* corect */

Deasemenea utilizarea operatorilor de incrementare/decrementare la apelul macrourilor trebuie evitata atunci


cand parametrul corespunzator apare de mai multe ori in sirul de substitutie. De exemplu:

a2=square(a++);

se va expanda ca

a2=(a++)*(a++);

ceea ce e diferit de

a2=(a)*(a);
a++:

38
Directive pentru conditionarea actiunilor preprocesorului/compilatorului
#idef NUME

#endif

#ifndef NUME

#endif
#if expresie_conditionala

#endif

#if !expresie_conditionala

#endif

Exemple:

#if !defined(HDR)
#define HDR

/* contents of hdr.h go here */

#endif
sau

#ifndef HDR
#define HDR

/* contents of hdr.h go here */

#endif

sau
#if SYSTEM == SYSV
#define HDR "sysv.h"
#elif SYSTEM == BSD
#define HDR "bsd.h"
#elif SYSTEM == MSDOS
#define HDR "msdos.h"
#else
#define HDR "default.h"
#endif
#include HDR

39
Pointeri

Preliminarii: adrese (de memorie)

Prin locatie de memorie vom intelege, in continuare, cea mai mica unitate de memorie adresabila. De obicei,
unitatea minima de memorie adresabila este byte-ul. De obicei un byte este format din 8 biti (un bit este unitatea
minima de reprezentare a datelor), caz in care se vorbeste despre octet.
Locatiile de memorie sunt consecutive si numarul lor de ordine (numar intreg pozitiv!) reprezinta adresa lor .
Exista o stransa legatura intre dimensiunea memoriei adresabile si numarul de biti pe care se reprezinta adresele
locatiilor de memorie. De ex. cu adrese pe 2 octeti (16 biti) se poate adresa un spatiu de memorie de maximum
216=65536 locatii (64KB). Cu adrese pe 4 octeti se poate adresa un spatiu de memorie de 2 32=4294967296 locatii
(4GB).

Informatiile memorate de un calculator (fie ele date sau coduri de instructiuni sau chiar adrese) pot ocupa una sau
mai multe locatii succesive de memorie. Indiferent insa cate locatii de memorie ocupa o informatie ce trebuie
stocata, adresa de inceput a grupului de locatii ocupate este…o adresa. Altfel spus, informatiile de tip adresa
ocupa acelasi numar de octeti, indiferent de tipul informatiei stocate incepand cu acea adresa. De obicei adresele
se reprezinta pe doi sau patru octeti. Adresele reprezentate pe doi octeti sunt adrese relative in cadrul unor
segmente de memorie de 64KB, si sunt utilizate de obicei la calculatoare cu registri pe 16 biti, necesitand o
schema de prelucrare care sa permita accesarea unui spatiu de memorie mai mare decat 64KB.
In exemplul de mai jos, adresele (de inceput ale) celor trei date sunt cele cu font ingrosat. Data de tip long int
ocupa 4 bytes (de la 0100 la 0103, inclusiv), data de tip char ocupa un byte (la adresa 0106) iar data de tip int
ocupa 2 bytes (de la adresa 010A la 010B, inclusiv).

0100
0101
0102
0103
0104
0105
} long int (4 bytes)

0106 } char (1 byte)


0107
0108
0109
010A
010B }int (2 bytes)

Ce sunt? Cum se declara? La ce sunt buni?

Definitie1 (incompleta). Pointerii sunt variabile.

Ca orice variabila, o variabila pointer ocupa spatiu in memorie. Spatiul ocupat de o variabila pointer este
dependent de implementare, este raportat corect de operatorul sizeof si este egal cu numarul de octeti necesari
pentru reprezentarea unei adrese, pentru ca:

40
Definitie2 (incompleta). Pointerii sunt variabile a caror valoare este interpretata ca adresa a unei
locatii de memorie.

Sintaxa declaratiei unei variabile pointer este:

[modificatori] NUME_TIP *nume_variabila;

unde NUME_TIP este orice tip de data fundamental sau definit de catre programator iar nume_variabila este un
identificator de variabila.

La ce sunt « buni » pointerii ? Un pointer permite accesul la o valoare, prin intermediul adresei de inceput a zonei
de memorie in care e stocata. (Adresa de inceput, pentru ca, cu exceptia datelor de tip char, toate celelalte
tipuri de date ocupa mai multi bytes in memorie !).
Numele de tip precizat in declaratia unei variabile pointer indica tipul (si deci si numarul de octeti al) valorii care
poate fi accesata prin intermediul pointerului care contine adresa sa.

Definitie3 (completa). Pointerii sunt variabile a caror valoare este interpretata ca adresa a unei
locatii de memorie unde poate fi accesata o valoare de tipul declarat al pointerului.

Pointerii, fiind variabile, au aceleasi atribute ca orice alta variabila : clasa de memorare, domeniu, vizibilitate,
durata, linkage.
La fel ca in cazul oricaror variabile, variabilele pointer globale sau cele locale declarate static se initializeaza
automat, cu valoarea NULL. NULL, ca valoare a unui pointer, este o adresa invalida! Ea se foloseste doar pentru
a indica faptul ca pointerul care are valoarea respectiva nu poate fi utilizat intr-o operatie de dereferentiere.

Operatii de baza: initializarea pointerilor: operatorul &. De ce?


Operatorul unar & se uilizeaza in expresii a caror valoare reprezinta adresa variabilei operand.

Ex.
TIP var, *pvar; /* var – variabila de tipul TIP; pvar – variabila “pointer la variabile de
tipul TIP” */
pvar = &var; /* valoarea lui pvar reprezinta adresa variabilei var */

OBS. TIP poate fi oricare din tipurile fundamentale sau definite de catre utilizator (inclusiv pointer!)

Operatii de baza: dereferentierea pointerilor. Operatorul *.


Operatorul unar * se utilizeaza in expresii a caror valoare reprezinta valoarea memorata la adresa indicata de
operand (variabila pointer).

Exemplu:

TIP var=expresie,*pvar; /* var – variabila de tipul TIP; pvar – variabila “pointer la variabile de tipul TIP” */
pvar = &var; /* valoarea lui pvar reprezinta adresa varibilei var */
*pvar /* *pvar este o valoare de tipul TIP egala cu valoarea espresiei expresie*/

41
Exceptie : pointeri (void *)
Daca tipul declarat al unui pointer este void *, aceasta semnifica absenta oricarei informatii despre tipul valorii
memorate incepand cu adresa care reprezinta valoarea pointerului. Astfel de pointeri nu pot fi folositi decat
pentru memorarea unor adrese nu si pentreu accesul la datele de la adresele respective!

Asupra pointerilor de tip void * nu se pot face nici un fel de operatii (din cele admise asupra pointerilor) cu
exceptia asignarii unui astfel de pointer la alt pointer sau al asignarii unui pointer la un astfel de pointer.
Ex:
int v, *pi=&v;
char *pc;
void *p;
p=pi; /*corect! p contine adresa variabilei v */
*p… /* eronat! p nu poate fi dereferenetiat pt ca nu se cunoaste tipul datei de al adresa p*/
pc=p: /* corect! pc contine adresa primului octet (un char) de la adresa din p=pi deci
primul octet al lui v */

De ce pointeri ?
Pare fireasca intrebarea : de ce era nevoie de pointeri ? In fond, orice valoarea oricarei variabile este accesibila
utilizand numele (identificatorul) variabilei! Intrebarea are doua raspunsuri :
a) pentru ca permit sa se realizeze lucruri imposibil de realizat altfel
- accesul direct la variabile locale functiei apelante
- alocarea de spatiu in mod dinamic
b) pentru ca permit obtinerea de cod mai eficient
- in cazul sortarii unei multimi de siruri de caractere sau al unei multimi de structuri
- in cazul prelucrarii tablourilor

Pointeri ca argumente de functii


Argumentele formale declarate ca tablouri sunt pointeri! Intr-adevar, daca un argument formal al unei functii este
un tablou, la apelul functiei respective ceea ce se transmite este adresa tabloului. In functia apelata, argumentul
respectiv este insa o variabila al carei continut este o adresa. Dar, variabilele al caror continut (valoare) reperzinta
o adresa sunt (se numesc)…pointeri!

OBS. De altfel, la apelul unei astfel de functii, argumentul actual corespunzator argumentului formal declarat ca
tablou este, de obicei, numele unui tablou. Numele unui tablou este sinonim cu adresa de inceput a tabloului,
Q.E.D.!

Pe de alta parte daca functiei apelate i se transmite ca argument adresa unei variabile locale functiei apelante
(deci argumentul formal corespunzator este declarat ca pointer !) functia ape lata are acces direct la varibila a
carei adresa i-a fost transmisa !

Operatii cu pointeri. Pointeri si tablouri. Indexarea si dereferentierea –


notatii echivalente.
Operatii legale asupra pointerilor:
 Asignarea unei adrese la un pointer
 Atribuirea unui pointer la alt pointer
 Dereferentierea unui pointer (daca e de alt tip decat void *!)
 Adunarea/scaderea unei constante la/dintr- un pointer (atunci cand pointerul indica spre un
element al unui tablou)

42
 Incrementarea/decrementarea unui pointer (atunci cand pointerul indica spre un element al unui
tablou)
 Scaderea unui pointer din alt pointer (atunci cand ambii indica spre elemente ale aceluiasi
tablou)
 Compararea a doi pointeri care indica spre elemente ale aceluiasi tablou

Operatii ilegale
 Oricare din operatiile care nu sunt legale! ;-)

Pointeri si siruri de caractere.


Exista o stransa legatura intre pointeri si tablouri. De obicei (desi nu in exclusivitate!), pointerii se utilizeaza in
conjunctie cu tablourile (pentru a accesa elemente ale unui tablou). Cu o frecventa si mai mare se utilizeaza
pointerii pentru prelucrarea sirurilor de caractere (tablouri – declarate ca atare sau nu! – al caror continut este o
succesiune de caractere din care ultimul are valoarea ‘\0’ , asa numitul caracter terminator de sir). Toate functiile
de tratare a sirurilor de caractere (care au prototipul in headerul string,h) reprezinta exemple de prelucrare prin
intermediul pointerilor.

Pointeri vs. tablouri


Tinand cont de faptul ca numele unui tablou reprezinta un sinonim pentru adresa unui tablou, se poate utiliza
numele unui tablou ca pointer.

Ex.
TIP t[DIM], *p=t;

t[i] este tot una cu *(t+i) /* t+i reprezinta adresa celui de-al i+1 –lea element al tabloului t */

Pe de alta parte, intrucat evaluarea oricarei expresii reprezentand un element de tablou presupune un calcul de
adresa, un pointer poate fi utilizat ca nume de tablou.

Ex.
t[i] este totuna cu *(t + i*sizeof(TIP)) care este totuna cu *(p+i) si ca atare cu p[i]

OBS. Echivalenta dintre numele de tablouri si pointeri se opreste insa acolo unde e vorba de operatii care ar avea
ca urmare modificarea adresei.
Ex.
p++;
p=p+i;

sunt ambele expresii corecte, pe cand

t++;
t=t+i;

sunt ambele eronate, intrucat presupun modificarea lui t care este o constanta (adresa de memorie de la care
incepand sunt memorate elementele tabloului!

Eficienta utilizarii pointerilor.


Pointerii, desi contribuie la scaderea lizibilitatii programelor C reprezinta in anumite situatii o alternativa mult
mai eficienta decat utilizarea tablourilor. Pe de o parte utilizarea pointerilor poate micsora semnificativ timpul de
acces la elementele unui tablou iar pe de alta parte, utilizarea pointerilor reprezinta singura modalitate de acces la
zone de memorie alocate dinamic (in timpul executiei).

43
Ex.

for(i=0; i < DIM ; i++) /* la fiecare iteratie: o comparatie si o incrementare */


t[i]=expresie; /*o inmultire, o adunare si o asignare (in afara de operatiile presupuse de
evaluarea expresiei)*/

for(p=t,i=0;i<DIM;i++,p++) /* la fiecare iteratie: o comparatie si doua incrementari */


*p=expresie; / * o asignare (in afara de operatiile presupuse de evaluarea expresiei)*/

OBS. Diferenta: o inmultire si o adunare la varianta cu tablou fata de o incrementare la varianta cu pointer. Chiar
daca presupunem ca incrementarea este echivalenta ca timp de executie cu adunarea (in nici un caz nu este mai
lunga !) in versiunea in care se foloseste pointerl se economiseste timpul necesar unei inmultiri la fiecare ciclu !
Daca in expresie mai apar si alte referiri la elemente ale tabloului diferenta creste.

Pointeri la functii
Desi functiile nu sunt variabile, ele ocupa totusi o zona de memorie, cu alte cuvinte, incep de la o anumita adresa!
De aceea, in C se pot declara pointeri la functii, prin intermediul carora se pot apela functiile a caror adresa o
contin. In mod similar numelor de tablouri, numele unei functii este sinonim cu adresa tabloului.

Ex.
int (*pf)(char *); /*pointer la functie care asteapta ca argument un char * si care intoarce un int */
pf=strlen; /* initializarea pointerului cu adresa unei functii de tipul declarat al pointerului */

n=(*pf)(“Timisoara”); /* apelul functiei strlen prin intermediul pointerului pf */

Tablouri multidimensionale. Tablouri de pointeri

Spre deosebire de alte limbaje, in C un tablou multidimensional este un tablou …unidimensional ale carui
elemente sunt …tablouri!
Ex.
TIP t[N][M];
In exemplul anterior, t este un tablou de N elemente, fiecare element fiind un tablou de M elemente de tipul TIP!
Aceasta inseamna ca t[i] este un tablou de M elemente de tipul TIP.
La fel ca si in cazul tablourilor unidimensionale, dimensiunile (precizate in declaratia) tablourilor
multidimensionale trebuie sa fie expresii constante.
In cazul in care un tablou multidimensional este transmis ca argument unei functii, prima dimensiune nu trebuie
precizata in declaratia functiei, in schimb toate celelalte dimensiuni sunt obligatorii, pentru a permite
compilatorului sa calculeze corect adresa unui element atunci cand se foloseste indexarea

TIP fct( TIP t[ ][N2][N3]);

Aceasta necesitate rezulta clar din modul in care o expresie desemnand un element de tablou este folosita pentru
a calcula adresa acestuia:
Elementul t[i][j][k] se afla la adresa t + ( i – 1)*N2*N3 + j.

Pointerii fiind variabile, se pot agrega (grupa) in tablouri.


Declaratia unui tablou de pointeri arata ca mai jos:

TIP *ptr_tab[N];

44
Tablourile de pointeri reprezinta alternativa (economica) la utilizarea tablourilor bidin\mensionale, mai ales a
tablourilor bidimensionale de caractere.
Ex (reprez. Grafica)…

Transmiterea argumentelor pe linia de comanda

Un ui program C I se pot transmite anumite argumente (siruri de caractere) la lansarea in executie. Pentru
aceasta, functia main trebuie declarata ca asteptand doua argumente:
- argc – un intreg care are ca valoare numarul de argumente cu care a fost lansat in executie
programul (inclusiv numele acestuia)
- argv – un tablou de pointeri catre sirurile de caractere care reprezinta argumentele

Ex.
main( int argc, char *argv[ ])
{
/* definitia functiei main, in care se utilizeaza argumentele spre care pointeaza argv pentru a obtine
comportamente specifice, in functie de valoarea acestora */

}

45
46
Structuri si uniuni
Structuri. Declaratie.
Structurile sunt tipuri de date agregate care grupeaza mai multe date, in general de tipuri diferite – desi nu
obligatoriu! – care au o caracteristica comuna: descriu diverse atribute ale aceleiasi entitati.
Ex. Datele unui angajat dintr-o intreprindere, datele care definesc o adresa (domiciliu) coordonatele unui punct
din spatiul bi, tri, n-dimensional, etc. Structurile sunt echivalente cu tipuri de date similare din alte limbaje,
precum tipul record din Pascal.
Intrucat fiecare tip de “obiect” care se modeleaza printr-o structura are atribute distincte, structurile nu sunt de
fapt tipuri concrete ci permit definirea (de catre programator) a caracteristicilor tipului respectiv. Caracteristicile
respective alcatuiesc un “sablon”, o matrita care specifica atributele tuturor obiectelor de acelasi tip.

Structurile (ca tip de date specifice) se declara in felul urmator:

struct eticheta_tip {
TIP_membru1 nume_membru1;
TIP_membru2 nume_membru2;
...
TIP_membrun nume_membrun;
};

Cuvantul cheie struct precizeaza ca declaratia care urmeaza este pentru un nou tip de date ale carui atribute sunt
precizate de membrii structurii. Un anumit tip de structura poate fi precizat, de exemplu pentru a declara instante
ale acelei structuri (variabile), cu ajutorul etichetei care precizeaza ce tip de structura se declara

Operatii cu structuri. Accesul la membri

O structura poate fi initializata cu Ajutorul unei liste de initializatori, cate unul pentru fiecare membru. Exemplu
struct s{ /* declaratia tipului struct s */
char c;
int j;
float f;
char string[10];
} ;
struct s v={‘a’, 1, 3.14, “text”}; /*declaratia si initializarea variabilei v de tipul struct s*/

O structura poater fi asignata altei structuri, ca o entitate;


struct s u;
u=v; /*membrii variabilei u au aceleasi valori ca si cei ai variabilei v */

Variabilele de tip structura pot fi transmise ca argumente la functii si pot fi returnate ca valori ale unei functii.
struct s funct(struct s x); /*declaratie de functie care are un argument de tip struct s */
/* si returneaza o valoare de tip struct s */
u=funct(v); /*apelul functiei si asignarea valorii returnate la o variabila de tip struct s*/

Variabilele de tip structura nu sunt utile daca nu pot fi acesati, individual, membrii lor. Accesul la membrii unei
.
variabile de tip structura se face cu ajutorul operatorului (punct).
Generic:
nume_variabila.nume_membru /* valoarea membrului nume_membru al variabilei
/* cu numele nume_variabila */
Ex. v.c /* membrul c al variabilei v */

47
u.c /* membrul c al variabilei u */

Structuri imbricate

Membrii unei structuri pot fi structuri (de un tip diferit!).

Ex.
struct point { int x, y;};
struct rectangle { struct point p1,p2};

struct s{ struct s m;}; /* eroare: membrii unei structuri nu pot fi structuri de acelasi tip*/
OBS. Interdictia/imposibilitatea declararii de structuri avand ca membri structuri de acelasi tip rezida din
caracterul recursiv al unei astfel de declaratii, ceea ce determina imposibilitatea rezolvarii uneia din sarcinile
legate de declararea variabilelor de tipul respectiv: rezervarea de spatiu! (La declararea oricarei variabile se
rezerva si spatiu pentru acea variabila!)

Pointeri la structuri. Tablouri de structuri. Tablouri de pointeri la structuri


Variabilele de tip structura fiind… variabile, ele ocupa o anumita zona de memorie si ca atare au o adresa –
adresa de inceput a zonei de memorie unde este stocata variabila respectiva. Adresa unei variabile structura se
poate memora intr-o variabila de tipul pointer la o structura de acel tip.

Ex.
struct s {
...
}v, *pv=&v;

Cu declaratia de mai sus, v este o variabila de tipul struct s, pv este un pointer la o structura de tipul struct s, iar
*pv este tot una cu v (daca pv are ca valoare adresa lui v) adica o structura de tip struct s.

Accesul la membrii unei structuri, prin intermediul unui pointer care contine adresa variabilei respective se face
in felul urmator:

(*pv).nume_membru

din cauza precedentei mai mari a operatorului de acces la membrii unei structuri (.) decat a operatorului de
dereferentiere (*).
Exista o alternativa pentru accesul la membrii unei structuri prin intermediul unui pointer si anume prin utilizarea
operatorului ->
Ex.
pv->nume_membru

Deasemenea, structurile (de acelasi tip) se pot agrega in tablouri.


Ex.
struct s {
...
} t[DIM];

In exemplul de mai sus, t este adresa tabloului de structuri, t[i] este


elementul de indice i al tabloului, adica o variabila de tip struct s, iar
accesul la membrii unui element al tabloului se face prin expresii de tipul

t[i].nume_membru

48
Intrucat pointerii se folosesc cu precadere pentru a indica/accesa elemente
de tablou, in exemplul urmator se prezinta modalitatea de a accesa membrii
unui element al unui tablou de structuri prin intermadiul unui pointer.

Ex.
struct s t[DIM], *p=t;
...
(p+i)->nume_membru /*acces la un membru al elementului de indice i*/
p->nume_membru /*acces la un membru al elemnetului “curent”*/

Structuri cu autoreferire. Liste, arbori, tabele de cautare (hashing)

Structurile reprezinta metoda preferata pentru implementarea listelor, arborilor, tablourilor de cautare (vezi si
discutia referitoare la Probleme care implica utilizarea tablourilor sau a listelor).

Structurile nu pot contine, ca membri, structuri de acelasi tip! Explicatia consta in imposibilitatea rezervarii de
spatiu pentru o variabila de acest tip, dupa cum rezulta din exemplul urmator:

struct s {
char c;
struct s n;
} v;

Oridecate ori compilatorul intalneste o declaratie (de definire) a unei variabile, una din actiunile intreprinse o
reprezinta generarea de cod pentru rezervarea de spatiu (“crearea variabilei”) pt variabila respectiva. In cazul unei
variabile structura, aceasta implica rezervarea de spatiu pentru fiecare membru in parte…

v.c - 1 byte
v.n - variabila structura
v.n.c - 1 byte
v.n.n - variabila structura
v.n.n.c - 1 byte
v.n.n.n - variabila structura…
…si procesul nu poate fi terminat!
In schimb, o structura poate contine ca membru un pointer la o structura de acelasi tip

struct s {
char c;
struct s *next;
} v;

v.c - 1 byte
v.next - 2 (4) bytes

si procesul de rezervare de spatiu s-a incheiat!

Structurile care contin ca membri pointeri spre structuri de acelasi tip se numesc structuri cu autoreferire.
Structurile cu autoreferire reprezinta mijlocul de implementare a listelor, arborilor, tabelelor de cautare sau
hashing.

49
Operatorul typedef

Limbajul C permite definirea de noi nume de tipuri pentru tipuri de date existente. Aceasta facilitate este
accesibila prin operatorul typedef. Sintaxa operatorului este:

typedef nume_tip_existent nume_nou_de_tip;

OBS. Noul nume de tip, creat cu typedef poate fi folosit in acelasi mod ca si vechiul nume, cu care este sinonim!

Ex.
typedef int Coord; /* Coord este un intreg */
typedef struct tnode * Treeptr; /*Treeptr este un pointer la struct tnode */
typedef char * String; /* String este un pointer la char */

OBS. In ultimul exemplu, desi String este sinonim cu un pointer la char si deci poate fi folosit (legal) in :

char c;
String p=&c;

Dar o astfel de utilizare ar fi improprie, intrucat, string inseamna “sir” (de caractere, in context!), ceea ce nu e …
cazul in exemplul anterior! Exemplul urmator indica o utilizare pertinenta pentru String:

char t[ ] = “Timisoara”;
String p = s, q;
Q = (String)malloc( strlen(p)+1);

intrucat, valoarea lui p este intradevar adresa unui sir (de caractere)!

Numele “create” cu typedef au aceeasi semnificatie ca si sinonimele lor si pot fi utilizate oriunde se poate utiliza
un nume de tip predefinit!

Uniuni. Declaratie

Uniunile sunt tipuri de date care permit partajarea aceluiasi spatiu de memorie de catre mai multe variabile (de
tipuri diferite) care nu trebuie sa coexiste (simultan). Sintaxa declaratiei unei uniuni:

union eticheta {
/*
declaratii membri
*/
};

Ca si in cazul structurilor, declaratia unei uniuni precizeaza un “model”/”sablon” si ca atare nu este insotita de
rezervare de spatiu. Spatiu se rezerva doar in momentul declararii unei variabile de tip uniune.
Spatiul necesar unei variabile uniune trebuie sa fie (este) suficient pentru a pastra cel mai mare (care are nevoie
de cel mai mult spatiu) membru.

50
Initializarea (pe linia de declaratie a) unei variabile uniuni se poate face doar cu o valoare corespunzatoare
primului membru.

Operatii cu uniuni

Ca si in cazul structurilor, variabilele uniune pot fi asignate/copiate (inclusiv transmise ca argumente actuale la
apelul unei functii, respectiv returnate de o functie), li se poate prelua adresa si membri sai pot fi accesati
individual. Operatorul de acces la membri este acelasi ca la structuri, adica .
Cade in sarcina programatorului sa urmareasca care dintre membri variabilei uniune este memorat in aceasta.
Exista o modalitate standard pentru a realiza aceasta urmarire.
Ex.
#define INT 0
#define FLOAT 1
#define STRING 2

union u {
int ival;
float fval;
char *sval;
; v;
int v_type;

Oridecate ori se memoreaza un membru in variabila uniune v, actiuneaeste insotita de actualizarea valorii
variabilei v_type:

v.ival=val_intreaga;
v_type=INT;

v.fval=val_flotanta;
v_type=FLOAT;

v.sval=adresa_sir_caractere;
v_type=STRING;

Oridecate ori trebuie “exploatata” valoarea variabilei uniune, se verifica tipul valorii memorate curent in v prin
“interogarea” variabilei v_type:

if(v_type == INT)
printf(“ %d “, v.ival; /* “exploateaza” pe v.ival */
else if (v-type == FLOAT)
printf(“ %f “, v.fval); /* “exploateaza” pe v.fval */
else if(v_type == STRING)
printf(“ %s “, v.sval; /* “exploateaza” pe v.sval */
else
printf(“bad union member! “);

51
Probleme care implica utilizarea tablourilor sau a listelor
Cazul 1
Se cunoaste dimensiunea tabloului (volumul de date) la momentul elaborarii/proiectarii/codificarii programului.

Solutie: tablou cu dimensiunea “inghetata” in fisierul sursa (eventual in fisierul sursa sau intr-un fisier header
se defineste cu directiva define dimensiunea tabloului , astfel incat daca se doreste o versiune a aplicatiei care sa
permita un volum diferit de date se modifica definitia).

Cazul 2
Se cunoaste volumul de date de abia la inceputul executiei programului (pentru fiecare executie volumul de date
e posibil sa difere).

Solutie: “tablou” pentru care se aloca spatiu in mod dinamic si accesul la elementele tabloului se face de regula
prin pointeri.

Cazul 3
Volumul de date nu se cunoaste nici macar la inceputul executiei ci doar la sfarsitul fazei de creere/citire a
datelor.

Solutie: datele se implementeaza sub forma de lista/arbore/graf/retea (in functie de problema concreta)

52
Intrari si iesiri (I/O). Fisiere.

Intrari/iesiri standard

La lansarea in executie a oricarui program se “deschid” in mod automat 3 dispozitive/fisiere/streamuri standard:


 stdin (standard input): dispozitivul standard de intrare, asociat in mod implicit cu
tastatura
 stdout (standard otuput): dispozitivul standard de iesire, asociat in mod implicit cu
display-ul
 stderr (standard error): dispozitivul standard pentru comunicarea mesajelor de
eroare, asociat intotdeauna cu display-ul

Stdin si respectiv stdout pot fi redirectate astfel incat intrarea sa se faca dintr-un fisier, respectiv iesirea sa se
faca intr-un fisier.

Fisiere. Modele
Doua modele:
 Modelul UNIX: fisiere, in care accesul se face la nivel de byte, comanda dispozitivelor fizice se
face de catre sistemul de operare (SO), singura legatura a programului cu fisierul fiind handle-
ul (un numar intreg nenegativ returnat de functia de deschidere fisier – open). Caracteristici:
simplitate, eleganta dar si eficienta mai scazuta. Se pot folosi pentru a “construi” functii de I/O
de nivel superior.

 Modelul cu streamuri: streamuri (dispozitive logice de I/O), pentru care functia de deschidere
(fopen) pregateste un bloc de control al fisierului (FCB, o structura de tip FILE, prin
intermediul caruia se efectueaza/gestioneaza toate operatiile asupra fisierului) si intoarce un
pointer (file pointer) spre acest bloc de control. Blocul de control al fisierelor se pastreaza in
memoria programului ceea ce constituie principalul dezavantaj al modelului: din eroare, se
poate altera continutul FCB, pentru ca este accesibil! Avantaj: este mai eficient si este adaptabil
diverselor tipuri de SO.

Standardul C promoveaza modelul cu streamuri tolerand insa si modelul UNIX

Modelul cu streamuri. Operatii tipice cu streamuri


Pentru a putea opera asupra unui stream/fisier, acesta trebuie “deschis” (fopen), adica trebuie conectat
programul la dispozitivul de I/O vizat.

Operatiile asupra unui stream deschis se pot rezuma astfel:


 Citire
 Scriere
 Actualizare
 Adaugare

Pe de alta parte operatiile de acces la continutul unui fisier se poate face buferizat (prin intermediul unui buffer)
sau nebuferizat (direct, byte cu byte).

Un stream asupra caruia nu se mai efectueaza operatii trebuie “inchis” (fclose).

In functie de continut, fisierele sunt de doua tipuri:

53
 Fisere text, care sunt “colectii” de linii de text, fiecare linie terminata cu un caracter newline (intern,
pentru ca extern/fizic, terminatorul de linie poate fi altul si e sarcina implementarii sa asigure
compatibilitatea/conversia).
 Fisiere binare, care sunt colectii de bytes, a caror interpretare cade exclusiv in sarcina programului.

Clasificare a functiilor standard de I/O (modelul cu streamuri)


Functii de I/O formatata
 fprintf
 fscanf
 printf
 scanf
 sprintf
 sscanf
 vfprintf
 vprintf
 vsprintf

Functii de I/O la nivel de caracter


 fgetc
 fputc
 fgets
 fputs
 getc
 putc
 getchar
 putchar
 gets
 puts
 ungetc

Functii de I/O cu acces direct


 fread
 fwrite

Functii de pozitionare in fisiere


 fgetpos
 fsetpos
 fseek
 ftell
 rewind

Functii pentru gestionarea bufferelor


 fflushall
 setbuf
 setvbuf

Functii pentru tratarea a erorilor


 clearerror
 feof
 ferror
 perror

54

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