Sunteți pe pagina 1din 104

Introducere

Aprut n 1972, limbajul C este un limbaj de nivel nalt cu


utilizare universal (neorientat spre un domeniu limitat). Autorii
acestui limbaj sunt Dennis M. Ritchie i Brian W. Kernighan de la Bell
Laboratories.
Limbajul C a fost proiectat n ideea de a asigura implementarea
portabil a sistemului de operare UNIX. Un rezultat direct al acestui
fapt este acela c programele scrise n limbajul C au o portabilitate
foarte bun.
n mod intuitiv, spunem c un program este portabil dac el poate
fi transferat uor de la un tip de calculator la altul. n principiu, toate
limbajele de nivel nalt asigur o anumit portabilitate a programelor.
n prezent, se consider c programele scrise n C sunt cele mai
portabile.
Dup 1980, limbajul C a fost utilizat de un numr tot mai mare de
programatori, cunoscnd o rspndire spectaculoas. n prezent are o
poziie dominant n domeniul limbajelor de programare. Principalele
caracteristici care au asigurat succesul su, sunt:
{ Se utilizeaz pentru aplicaii tiinifice i tehnice, cu faciliti
grafice;
{ Permite accesul la toate resursele hardware ale calculatorului pn
la nivel de locaie de memorie, ceea ce se obine n general numai
n limbaj de asamblare;
{ Este un limbaj profesional, care prezint att avantajele unui
limbaj de nivel nalt ct i avantajele limbajelor de nivel cobort
(l. main, l.de asamblare);
{ Este un limbaj structurat sau procedural (ca i Pascal) i bazat pe
compilator; forma .exe a unui program poate fi rulat fr o nou
complilare;
{ Unitatea de baz a unui program n C este funcia; limbajul
dispune de o mare varietate de clase de funcii predefinite, puse la
dispoziia utilizatorului, fiind grupate n biblioteci;
{ Se consider c efortul pentru scrierea unei aplicaii n C este mai
mic dect n Pascal dar durata de nvare este mai mare.
Odat cu evoluia limbajului (i rspndirea sa) a fost necesar
adoptarea unui standard internaional care s defineasc limbajul,
biblioteca de funcii i s includ o serie de extensii utile, impuse de
marea varietate de aplicaii. Limbajul C - standard sau C - ANSI

(American National Standard Institute) a aprut n 1988 i reprezint


varianta inclus n toate dezvoltrile ulterioare ale limbajului (cu mici
excepii).
Anii '90 sunt consacrai programrii orientate spre obiecte (
Object Oriented Programming - OOP) la fel cum anii '70 au fost numii
anii programrii structurate .
Programarea orientat spre obiecte este un stil de programare
care permite abordarea eficient a aplicaiilor complexe, ceea ce se
realizeaz, n principiu, prin:
{ elaborarea de componente reutilizabile, extensibile i uor de
modificat, fr a reprograma totul de la nceput;
{ construirea de biblioteci de module extensibile;
{ implementarea simpl a interaciunilor dintre programe i sistemul
de calcul, prin utilizarea unor elemente standardizate.
n anii '80, interesul pentru programarea orientat spre obiecte a
crescut, ceea ce a condus la apariia de limbaje de programare care s
permit utilizarea ei. Limbajul C a fost dezvoltat i el n aceast
direcie, astfel c n 1980 a fost lansat limbajul C++. Acesta, elaborat
de Bjarne Stroustrup de la AT&T, este o extindere a limbajului C i
permite utilizarea principalelor concepte ale programrii orientate spre
obiecte.
Limbajul C++ a fost implementat pe microcalculatoare
compatibile IBM PC, n mai multe variante. Cele mai importante
implementri sunt cele realizate de firmele Borland i Microsoft.
Conceptele programrii orientate spre obiecte au influenat n
mare msur dezvoltarea limbajelor de programare n ultimii ani. Multe
limbaje au fost extinse astfel nct s permit utilizarea conceptelor
programrii orientate spre obiecte. Au aprut chiar mai multe extensii
ale aceluiai limbaj. De exemplu, n prezent asistm la extensiile
limbajului C++. O astfel de extensie este limbajul E ai crui autori
sunt Joel E. Richardson, Michael J. Carey i Daniel T. Shuh, de la
universitatea Wisconsin Madison.
Limbajul E a fost proiectat pentru a permite exprimarea simpl a
tipurilor i operaiilor interne, pentru sistemele de gestiune a bazelor
de date.
O alt extensie a limbajului C++ este limbajul O, dezvoltat la
Bell Laboratories. Cele dou limbaje (E i O) sunt n esen
echivalente.
Interfeele de utilizator au atins o dezvoltare mare datorit
facilitailor oferite de componentele hardware ale diferitelor tipuri de
calculatoare. n principiu, ele simplific interaciunea dintre programe
i utilizatorii lor. Astfel, diferite comenzi, date de intrare i rezultate

pot fi exprimate simplu i natural utiliznd ferestre de dialog, bare de


meniuri, cutii, butoane etc.
Implementarea interfeelor de utilizator este mult simplificat
prin utilizarea limbajelor orientate spre obiecte. Aceasta, mai ales
datorit posibilitilor de utilizare a componentelor standardizate aflate
n biblioteci specifice.
Firma Borland comercializeaz o bibliotec de componente
standardizate pentru implementarea interfeelor de utilizator folosind
unul din limbajele C++ sau Pascal. Produsul respectiv se numete
Turbo Vision.
De obicei, interfeele de utilizator gestioneaz ecranul n modul
grafic.
Una din cele mai populare interfee de utilizator grafice, pentru
calculatoarele IBM PC, este produsul Windows al firmei Microsoft.
Lansat n 1985, produsul Windows a parcurs mai multe variante i are
un succes fr precedent. ncepnd cu Windows 95, a devenit sistem de
operare, care a nlocuit treptat sistemul MS DOS.
Aplicaiile sub Windows se pot dezvolta folosind diferite medii
de programare, ca: Turbo C++, Pascal, Microsoft C++7.0, Microsoft
Visual Basic, Visual C i Visual C++.

1 Elemente de baz ale


limbajului C
1.1. Setul de caractere
Reprezint mulimea de caractere ce pot fi utilizate n textul unui
program. Setul de caractere al limbajului C este un subset al
standardului ASCII (American Standard Code for Information
Interchange ) i conine:
{ Literele mari: A, B, C, . . . , V, W , Z;
{ Literele mici: a, b, c, . . . , v, w, z;
{ Cifrele zecimale: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9;
{ Caractere speciale (29) :

. , ; : ? ' " ( ) [ ] { } ! | \ / ~ _ # % & ^ + - * = <


>
Alte caractere i semne pot s apar n comentarii, pot fi
constante de tip caracter sau iruri de caractere.
Caractere de tip spaiu (neimprimabile):
\n - newline (produce trecerea la nceputul rndului urmtor);
\t - tab. orizontal (introduce mai multe spaii libere succesive;
se utilizeaz pentru organizarea imprimrii pe coloane);
\v - tab. vertical (introduce mai multe rnduri libere pe
vertical);
\r - carriage return (deplasarea carului de imprimare la
nceputul rndului curent);
\f - form feed (salt la pagin nou).
Setul de caractere neimprimabile depinde de varianta limbajului.

1.2. Cuvinte rezervate


Sunt cuvinte n limba englez cu semnificaie predefinit. Ele nu
pot fi utilizate ca identificatori sau cu alt semnificaie dect cea
standard.
auto
break
case
char
const

double
else
enum
extern
float

int
long
register
return
short

struct
switch
typedef
union
unsigned

continue
for
signed
void
default
goto
sizeof
volatile
do
if
static
while
Cuvintele rezervate (sau cuvintele cheie) sunt utilizate n
construirea instruciunilor, declararea tipurilor de date sau drept
comenzi; n anumite situaii, pot fi redefinite (nlocuite cu alte
cuvinte).
Cuvintele rezervate se scriu cu litere mici.

1.3. Identificatori
Sunt nume date funciilor, variabilelor, constantelor, n vederea
utilizrii lor n programe.
Un identificator este o succesiune de litere i cifre, primul
caracter fiind obligatoriu o liter. Se pot utiliza literele mari i literele
mici din setul de caractere, precum i caracterul subliniere _
considerat liter.
Numrul de caractere din componena unui identificator nu este
limitat dar n mod implicit, numai primele 32 de caractere sunt luate n
consideraie. Aceasta nseamn c doi identificatori sunt diferii, numai
dac difer n primele 32 de caractere.
Standardul ANSI garanteaz 6 caractere / identificator.
Mediile de programare Turbo C i Turbo C++ permit
utilizatorului modificarea limitei de 32.
n limbajul C, literele mari difer de cele mici, aadar
identificatorii: aria_trg , Aria_trg , ARIA_TRG, difer ntre ei.
Se recomand ca identificatorii s fie nume sugestive pentru rolul
obiectelor pe care le definesc. De exemplu, funcia care calculeaz o
sum se va numi sum , cea care calculeaz factorialul, fact etc.
n mod tradiional, identificatorii de variabile se scriu cu litere
mici, constantele i macrodefiniiile cu litere mari etc.
Exemple:
Identificatori coreci

Identificatori incoreci

Observaii

d1, x1, x2, y11, alfa, beta

$contor

$ - caracter interzis

contor_1, data_16

struct

cuvnt cheie

delta_ec_2, tabel_nume

tab nr.2

conine un spaiu i punct

TipElementCurent

2abs, 5xx,

ncep cu o cifr

Observaii:
Cuvintele
cheie nu pot fi utilizate ca identificatori;
{

{ Pentru identificatori se utilizeaz numai setul de caractere C; ca


urmare, punctul, virgula, semnele de operaii etc., nu se utilizeaz
n construirea identificatorilor;
{ Identificatorii nu trebuie s conin spaiu liber;
{ Pentru un identificator format din mai multe cuvinte, fie se
utilizeaz liniua de subliniere, fie se insereaz litere mari pentru
separarea cuvintelor.

1.4. Comentarii
Sunt texte explicative plasate n textul programului, pentru a
facilita nelegerea lui i pentru a marca principalele aciuni.
Comentariile nu sunt luate n consideraie de ctre compilator i
ca urmare trebuie delimitate prin caractere speciale. Se delimiteaz
prin /* nceputul de comentariu i prin */ sfritul de comentariu.
Exemplu:
/* Acesta este un comentariu */
Dac se omite marcarea sfritului de comentariu, textul
programului (aflat dup comentariu) se consider ca fcnd parte din
acesta!
n limbajul C++ s-a mai introdus o convenie pentru a insera
comentarii i anume succesiunea :
//
marcheaz nceput de comentariu.
Comentariul delimitat prin // se termin pe acelai rnd pe care se
afl i nceputul lui, fr s mai fie necesar marcarea sfritului.
Observaii:
{ Comentariile pot fi introduse oriunde n programul surs;
{ Comentariile pot fi scrise pe una sau mai multe linii de text;
{ Nu este permis introducerea unui comentariu n interiorul altui
comentariu.
n general, se recomand introducerea de comentarii dup antetul
unei funcii, care s precizeze:
{ Aciunea sau aciunile realizate de funcie;
{ Tipul datelor de intrare i ieire;
{ Algoritmii realizai de funcie, dac sunt mai complicai;
{ Limite impuse datelor de intrare, dac este cazul.

1.5. Structura unui program

Un program conine una sau mai multe funcii. Dintre acestea,


una singur este funcia principal .
Fiecare funcie are un nume. Numele funciei principale este
main. Celelalte funcii au nume date de utilizator sau au nume
predefinit, dac sunt funcii standard, de bibliotec.
Programul se pstreaz ntr-un fiier sau n mai multe fiiere.
Acestea au extensia .c pentru limbajul C i .cpp pentru C++.
Un fiier care conine textul programului sau o parte a acestuia se
numete fiier surs. Prin compilarea unui fiier surs, compilatorul
genereaz un fiier obiect , cu extensia .obj .
Fiierele surs care intr n compunerea unui program pot fi
compilate separat sau mpreun. Fiierele obiect, corespunztoare unui
program, pot fi reunite ntr-un program executabil, prin editarea de
legturi (link - editare), rezultnd un fiier executabil, cu extensia .exe
.
Execuia unui program ncepe cu prima instruciune a funciei
main .
Dac funcia main( ) face apel la alte funcii, acestea vor intra n
aciune cnd vor fi chemate de main ( ).
Exemplu:
# include <stdio.h>
void main(void)
{
printf("C el mai simplu program C \n");
}
{ Prima linie este o directiv pentru compilator, pentru a include n
textul surs fiierul cu numele stdio.h (standard input output)
aflat ntr-un director predefinit. Directivele de compilator au
marcajul #.
{ A doua linie este antetul funciei main() - funcia principal.
Cuvntul void arat c funcia este fr tip (nu furnizeaz valori)
iar void dintre paranteze arat c funcia este fr parametri.
{ Corpul funciei este delimitat de paranteze acolade { }. Funcia
main conine un apel la alt funcie, printf() - funcie de
bibliotec. Ea realizeaz tiprirea sau afiarea unui mesaj,
conform unui anumit format (print format). Funcia printf este o
funcie de intrare/ieire, care se gsete n fiierul header stdio.h;
de aceea acest fiier trebuie inclus n toate programele n care se
fac operaii de citire / scriere.

n limbajul C, nu exist instruciuni de intrare / ieire (pentru


citire i scriere date) ca de exemplu read, write din Pascal sau Fortran.
Acest fapt se justific prin dependena instruciunilor de intrare/iere
de sistemul de operare. Autorii limbajului C au optat pentru realizarea
operaiilor de intrare / ieire cu ajutorul funciilor de bibliotec.
Semnul ; este separator de instruciuni (ca n Pascal).
Funcia printf() are un singur parametru = ir de caractere,
delimitat de ghilimele ". . . ". Ultimele caractere sunt de tip
neimprimabil i determin trecerea la linie nou, dup imprimarea
textului.

1.5.1. Structura unei funcii


n programul surs, o funcie are dou pri: antetul i corpul
funciei. O funcie poate avea un numr de parametri (variabile) dar o
singur valoare - valoarea funciei. Antetul conine informaii despre
tipul valorii funciei, numrul i tipul parametrilor i conine
identificatorul (numele) funciei.
Structura unei funcii:
<tip > <nume > (< lista declaraiilor parametrilor formali >)
{
<declaraii >
<instruciuni >
}
Primul rnd este antetul; acesta apare obligatoriu naite de corpul
funciei dar i la nceputul programului (nainte de main()) situaie n
care se numete prototip .
n cazul tipurilor standard, <tip> din antet este un cuvnt cheie
care definete tipul valorii returnate de funcie.
n C exist dou categorii de funcii:
{ funcii cu tip, care returneaz o valoare, de tipul <tip>;
{ funcii fr tip, care nu returneaz nici o valoare dup execuie,
dar efectueaz anumite prelucrri de date; pentru acestea se va
folosi cuvntul rezervat void n calitate de <tip>.
O funcie poate avea zero, unul sau mai muli parametri. Lista
declaraiilor parametrilor (formali) este vid cnd funcia nu are
parametri.
Exemple:
1.

void ff_1(void)

{
. . . .
}
Funcia ff_1 este fr tip, deci nu returneaz o valoare concret
i nu are parametri.
2.

void ff_2()
{
. . . .
}
Funcia ff_2 este fr tip i nu are parametri (variant de scriere).

3.

int ff_3()
{
. . . .
}

Funcia ff_3 returneaz o valoare de tip ntreg, dup execuie, dar


nu are parametri.

4.

float ff_4(int a, int b)


{
. . . .
}

Funcia ff_4 returneaz o valoare de tip real (floating point) i


are doi parametri de tip ntreg (funcie real cu dou variabile ntregi).
n corpul funciei pot s apar una sau mai multe instruciuni
return, care au ca efect revenirea n programul apelant. Dac nu apare
nici o instruciune return, revenirea n programul apelant se face dup
execuia ultimei instruciuni din corpul funciei.
n cazul n care funcia are mai muli parametri, declaraiile de
tip pentru parametri se separ prin virgul.
Parametrii se utilizeaz pentru a permite transferul de date, ctre
o funcie, n momentul utilizrii ei n program. La construirea unei
funcii, se face abstracie de valorile concrete. Acestea vor fi prezente
numai la execuia programului.
n momentul compilrii, este necesar doar cunoaterea tipurilor
valorilor pe care le vor primi parametrii la execuie. Aceste declaraii

de tip sunt indicate n antetul funciei i vor servi compilatorului s


rezerve memoria necesar pentru fiecare parametru.
Parametrii declarai n antetul unei funcii se numesc formali,
pentru a evidenia faptul c ei nu reprezint valori concrete, ci numai
in locul acestora, pentru a putea exprima procesul de calcul. Ei se
concretizeaz la execuie prin valori ce rezult din apelarea funciei,
cnd sunt numii parametri efectivi.
Observaii:
{ Dac nu se specific tipul unei funcii, se consider automat c ea
va returna o valoare de tip int .
{ n limbajul C++, controlul cu privire la tipul valorii unei funcii
este mai strict i ca urmare se recomand, ca tipul s fie efectiv
specificat, n toate cazurile.
{ Pentru funcia principal main, se pot utiliza antetele:
int main()
int main(void)
void main()
void main(void)
main()
main(void)
Primele dou antete arat c funcia returneaz o valoare
ntreag; de asemenea ultimele dou antete arat c se returneaz o
valoare ntreag, chiar dac nu se specific tipul int n antet.
Se utilizeaz foarte frecvent varianta fr tip main().
Funcia main poate avea i parametri.
1.5.2. Declararea variabilelor. Clase de alocare
Obiectele de baz ale limbajului sunt variabilele. Acestea se
declar la nceputul funciilor (variabile locale) sau n exteriorul
tuturor funciilor (variabile globale), precizndu-se clasa de alocare
(eventual, vezi cap.5), tipul, numele i valorile iniiale (eventual).
Pentru a putea folosi variabile ntr-un program, acestea trebuie
declarate. O declaraie de variabile conine obligatoriu un specificator
de tip (tipul de date) i o list de identificatori, de exemplu:
int a,b,c;
prin care se declar (se rezerv spaiu de memorie) trei variabile de tip
int (ntregi cu semn), numite a, b, c. Dimensiunea unui tip de date
depinde de implementarea limbajului i se exprim prin numrul de
locaii de memorie (octei, un octet = 8 bii) necesare pentru stocarea
valorii.
Dimensiunea tipului int este 2 sau 4 octei (16 sau 32 de bii). Un
ntreg pe 16 bii poate lua valori n domeniul [-32768, 32767].

Alte tipuri uzuale sunt:


char - tipul caracter, dim.=1 octet;
long - tipul ntreg lung, dim.=4 octei;
short - tipul ntreg scurt;
float - tipul real, simpl precizie, dim.=4 octei;
double - tipul real, dubl precizie, dim.=8 octei;
long double - tipul real, precizie extins, dim.=10 octei.
Variabilele pot fi iniializate cu valori, chiar la declarare. n
exemplul de mai jos, variabilele reale x i z sunt iniializate iar y, nu.
float x =10.07 , y, z = 2.0;
Programul urmtor produce tabelarea valorilor unei funcii de
dou variabile, n domeniul [0, 1]x[0, 1], cu pasul 0.1.
# include <stdio.h>
double fm(double, double);
void main(void)
{
double x, y, pas=0.1;
for(x=0.0; x<=1.0; x=x+pas)
for(y=0.0; y<=1.0; y=y+pas)
printf("x=%lf y=%lf f(x,y)=% lf \n ", x, y, fm(x, y));
{
double fm(double x, double y)
{
return (3.0*x*y + 1.0)/(1.0 + x + y + x*y);
}
Programul conine dou funcii: main() i fm(). Funcia
matematic fm() are doi parametri de tip real, doubl precizie i anume
x i y iar tipul valorii funciei este tot real, dubl precizie. Numele
funciei reprezint valoarea ntoars de aceasta, de exemplu, fm(0.0,
1.0)=0.5 este valoarea lui fm cnd argumentele sale iau valorile x=0 i
y=1.
Instruciunea return <expresie>, ntoarce valoarea expresiei n
programul apelant, aceast form fiind obligatorie la funciile cu tip.
Declaraia
double fm(double, double) ;
de la nceputul programului este un prototip al funciei fm(), care
descrie tipul funciei, numele i tipul fiecrui parametru. n acest mod,
funcia fm() este recunoscut n main(), chiar dac definiia ei se afl
dup main() .

n general, este indicat s se scrie prototipurile tuturor funciilor,


att ale celor definite n modulul curent de program, ct i ale celor
definite n alte module dar folosite n modulul curent.
Funciile de bibliotec sunt complet definite (inclusiv
prototipurile) n fiiere header, deci includerea acestor fiiere, asigur
i prezena prototipurilor. De exemplu, prototipul lui printf() este n
fiierul stdio.h .
n funcia princial main() se declar trei variabile, x, y i pas,
ultima fiind iniializat cu valoarea 0.1.
Cele dou instruciuni for implementeaz execuia repetat a
funciei printf(), pentru 11 valori ale lui x i 11 valori ale lui y, n total
funcia se repet de 11 x 11 = 121 de ori. Prima aciune (x = 0.0) este
o iniializare i se execut o singur dat. A doua expresie din for este
condiia de repetare: dac x<=1, execuia va fi reluat pentru valoarea
curent a lui x.
Ultima aciune (x = x + pas) este actualizarea variabilei x i se execut
la fiecare iteraie, dup execuia corpului ciclului. Primul ciclu for
conine un alt ciclu for, cu variabila y, care se va executa de 11 ori
pentru fiecare valoare a lui x. La fiecare execuie a ciclului cu y, se
tipresc valorile x, y, fm(x, y), cu ajutorul funciei printf().
irul de caractere din printf() conine specificatori de format,
anume secvenele %lf, care indic modul de interpretare a valorilor ce
se afiaz. Ele vor fi interpretate ca valori reale n dubl precizie.
Evident, numrul i tipul specificatorilor de format trebuie s coincid
cu parametrii care urmeaz dup irul ce descrie formatul. Restul
caracterelor din ir vor fi tiprite prin copiere. Secvena \n asigur
trecerea la rndul urmtor dup scrierea celor trei valori.

2 Tipuri de date, operatori i


expresii
Tipul scalar
(char )
{ caracter

Tipul structurat
{ tablou

{ ntreg

(int)

{ structur

{ real

(float )

{ uniune

{ adres
(pointer )

Tipul void (fr tip)


{ void

{ ir de caractere

{ enumerativ (enum )

2.1. Tipuri de baz (scalare)


Pentru utilizarea eficient a memoriei, limbajul C ofer mai multe
tipuri de reprezentare pentru numerele ntregi i reale:
{ pentru ntregi: int, short int, long int , cu sau fr semn;
{ pentru reale: float, double, long double .
Fiecare variabil sau funcie trebuie asociat cu un tip de date
nainte de a fi efectiv folosit n program, prin declaraii de forma:
int c1, k, m;
float x, y, w=21.7;
long double ff_1(double, double);
Pe baza acestor declaraii, compilatorul rezerv memorie pentru
fiecare variabil, corespunztor tipului su.
2.1.1. Tipul ntreg
Este o submulime a numerelor ntregi; se reprezint pe 1, 2 sau 4
octei, respectiv pe 8, 16 sau 32 de bii.
Tip
Domeniul valorilor (IBM - PC)
Lungime (bii)
int
short int

16
16

-32 768 . . . +32 767


-32 768 . . . +32 767

long int

32

-2 147 483 648 . . . +2 147 483 648

unsigned short
unsigned long

16
32

0 . . . 65 535 (fr semn)


0 . . . 4 294 967 295 (fr semn)

signed char

-128 . . . +127 (cu semn)

8
0 . . . 255 (fr semn)
unsigned char
Restriciile impuse de standardul ANSI sunt:
dim (short) m 16 bii ;
dim (int) m 16 bii ;
dim (long) m 32 bii ;
dim (short) [ dim (int) [ dim (long) ;
Observaie: declaraia short int este echivalent cu short iar
long int este echivalent cu long , fr s apar confuzii.
Constante de tip ntreg
Pot fi scrise n sistemul de numeraie zecimal (baza 10), octal
(baza 8) sau hexazecimal (baza 16).
O constant zecimal ntreag este un ir de cifre zecimale, cu
prima cifr diferit de zero. Constantele ntregi reprezentate pe 16 bii
sunt de tip int iar cele reprezentate pe 32 de bii sunt de tip long.
Dac dorim s reprezentm o constant ntreag pe 32 de bii,
dei ea se poate reprezenta pe 16 bii, constanta trebuie s aib sufixul
L sau l.
Constantele ntregi sunt, de regul, cu semn. Dac dorim s fie
fr semn, constanta se termin cu litera U sau u.
Constantele ntregi fr semn se utilizeaz pentru a economisi
memorie. Astfel, constantele de tip int din intervalul [32768, 64535] se
pstreaz n memorie pe 32 de bii, iar constantele de tip unsigned din
acelai interval se reprezint pe 16 bii.
Pentru a reprezenta constante ntregi de tip long, fr semn, se
utilizeaz sufixele echivalente: ul, lu, LU, UL.
O constant octal ntreag este o succesiune de cifre octale (0.
.7), prima cifr fiind un zero, nesemnificativ. Sufixele L, l, U, u, se pot
utiliza cu aceleai semnificaii ca la constantele zecimale.
O constant hexazecimal ntreag este o succesiune de cifre
hexazecimale (0. .9, A, B, C, D, E, F), sau (0. . 9, a, b, c, d, e, f)
primele dou caractere fiind 0x sau 0X. Sufixele L, l, U, u, se pot
utiliza cu aceleai semnificaii ca la constantele zecimale.
Exemple:
132
12345L
0400
04000L
0xabbf

constant
constant
constant
constant
constant

zecimal de tip int;


zecimal de tip long;
octal;
octal de tip long;
hexazecimal;

0X2ACFFBL
constant hexazecimal de tip long;
Indicarea tipului este util cnd se folosesc funcii de bibliotec
avnd argumente de tip long sau fr semn; tipul constantei trebuie s
fie compatibil cu tipul predefinit al argumentului.
Operaiile de citire i scriere a valorilor de tip ntreg, cu funciile
de bibliotec printf(. . .) i scanf(. . .), necesit specificatori de format
corespunztori.
Tipul
n zecimal
n octal
n hexazecimal
int
long
short
unsigned

%d
%ld
%hd
%du

%o
%lo
%ho
%ou

%x
%lx
%hx
%xu

Programul urmtor citete o valoare de tip zecimal i scrie pe


ecran aceeai valoare n octal i n hexazecimal:
# include <stdio.h>
void main()
{
int val;
printf("Introducei o constant de tip ntreg:");
scanf("%d",&val);
printf("Valoarea %d in baza 8 este: %o\n", val, val);
printf("Valoarea %d in baza 16 este: %x\n", val, val);
}
Tiprirea unei valori cu semn, cu formatul %d poate duce la
valori graite, dac valoarea nu se ncadreaz n domeniul int:
# include <stdio.h>
main()
{
unsigned short n1=40000, n2=400;
printf("n1 si n2 considerate ca intregi fara semn:\n");
printf("n1=%hu n2=%hu \n", n1, n2);
printf("n1 si n2 considerate ca intregi cu semn:\n");
printf("n1=%hd n2=%hd \n", n1, n2);
}
Programul are ca efect pe ecran:
n1 si n2 considerate ca intregi fara semn:
n1=40000
n2=400
n1 si n2 considerate ca intregi cu semn:
n1=-25536
n2=400

2.1.2. Tipul caracter


Este asociat cu tipul ntreg fr semn, pe 8 bii, deoarece fiecare
caracter are un numr de ordine, n codul ASCII, cuprins ntre 0 i
255.
O constant de tip caracter imprimabil se reprezint prin
caracterul respectiv, cuprins ntre semne apostrof: 'a', 'A', 'b', 'B', '7'
etc.
Limbajul C permite efectuarea de operaii aritmetice asupra unor
valori de tip caracter i se pot da valori numerice unor variabile de
acest tip. Exemplu:
char c, car;
//se definesc doua variabile caracter
. . . . . . .
car ='a';
// car = 97, codul lui 'a'
c = 97
// c = 97, deci c = car

2.1.3. Tipul real


Este o submulime a numerelor reale, mai precis, o submulime a
numerelor raionale. O constant de tip real se compune din:
{ o parte ntreag, numr ntreg cu semn (poate lipsi);
{ o parte fracionar, ir de cifre zecimale cu punct n fa;
{ un exponent, numr ntreg cu semn (poate lipsi).
Partea fracionar poate lipsi numai cnd este prezent partea
ntreag, meninnd punctul de separare: 2. sau 0. sau -314.
Exponentul este un ntreg cu sau fr semn, precedat de litera e
sau E i reprezint o putere a lui 10.
n mod implicit, constantele reale se reprezint n dubl precizie,
deci pe 64 de bii. Pentru economie de memorie, se poate opta pentru
reprezentare pe 32 de bii, prin adugarea literei f sau F la sfrit.
Pentru reprezentare pe 80 de bii, se adaug la sfrit litara L sau
l.
Exemple:
Constant real
Valoare
123.
123,0
-765.77

-765,77

.255
69e4

0,255
690 000,0

.23E-2

0, 0023

222.7e2
3377.5678e-5

22270
0,033775678

Variantele utilizate pentru tipurile reale sunt descrise n tabelul


urmtor.
Tip
Domeniul valorilor absolute
Lungime (bii)
32
float
3, 4 % 10 38 3, 4 % 10 38
64
double
1, 7 % 10 308 1, 7 % 10 308
3, 4 % 10 4932 3, 4 % 10 4932
80
long double
Fiierele header limits.h i float.h conin constante care definesc
implementarea tipurilor de baz.
Variabilele de tip real se declar prin specificarea tipului i listei
de identificatori:
float
val_1, val_f, zz, t, w;
double
xx_1, y;
long double
epsilon, pass_1;
Observaii:
{ Nu trebuie utilizate inutil variabile i constante de tip double sau
long double, deoarece determin creterea timpului de calcul i
ocup inutil memorie suplimentar;
{ Descriptorii de format la tiprire cu printf( ) sunt: %f sau %lf
pentru scriere normal, i %e sau %E, pentru scriere cu exponent.
Exemplu:
# include <stdio.h>
void main()
{
float nr_real=0.0057;
printf("nr_real=%f \n", nr_real);
printf("nr_real=%6.4f \n", nr_real);
printf("nr_real=%e \n", nr_real);
}
Rezultatul pe ecran este: nr_real=0.005700
nr_real=0.0057
nr_real=5.70000e-03
Codul 6.4 asociat cu %f realizeaz controlul formatului de scriere
prin indicarea spaiului total (6 caract.) i a prii fracionare (4
caract.).
Valorile de tip float sunt convertite automat la double de ctre
funcia printf( ), deoarece reprezentarea implicit a constantelor reale
este pe 64 de bii, deci double.

Valorile de tip long double necesit specificatori de format %Lf


sau % lf.
Constantele simbolice se definesc prin directiva #define, de
exemplu:
# define MAX 4500
//constanta simbolica de tip intreg
# define X_REF 37.99f //constanta simbolica de tip real
Constantele simbolice sunt substituite la compilare cu valoarile
prin care au fost definite. Se recomand scrierea lor cu litere mari.
Valoarea unei constante simbolice nu poate fi modificat la execuie.
2.1.4. Constante de tip ir de caractere
Un ir de caractere este o succesiune de caractere, eventual vid,
ncadrat de ghilimele ". . . . " , de exmplu:
"Limbajul C++ este un limbaj profesional"
n limbajul C, nu exist o declaraie special, ca de exemplu,
string n Pascal, pentru iruri de caractere. Acestea sunt considerate
tablouri de caractere, fiecare element al tabloului fiind un caracter,
care ocup n memorie un octet (8 bii).
Declararea datelor de tip ir de caractere, se face astfel:
char num_pr[20];
unde num_pr reprezint un identificator (nume i prenume) iar 20 este
lungimea irului, adic irul are maxim 20 de caractere. Compilatorul
adaug un caracter special, de sfrit de ir, neimprimabil, '\0'.
irurile de caractere pot fi scrise i citite cu funciile printf( ) i
scanf( ), specificatorul de format fiind %s.
# include <stdio.h>
void main()
{
char prenume[20];
printf("Care este prenumele tau? \n");
scanf("%s" , &prenume);
printf("Buna ziua %s \n", prenume);
}

2.1.5. Tipul pointer


Fiecrui obiect dintr-un program (variabil, constant, funcie), i
se aloc un spaiu de memorie exprimat n numr de locaii (octei,
bytes), corespunztor tipului de date prin care a fost definit obiectul
respectiv.
Localizarea (gsirea) unui obiect n memorie se face prin
intermediul adresei la care a fost memorat.
Adresa este un numr ntreg, fr semn i reprezint numrul de
ordine al unei locaii de memorie; n C adresele sunt de regul de tip
long int, domeniul int fiind insuficient.
Datele de tip adres se exprim prin tipul pointer. n limba
romn, se traduce prin referin, reper, localizator, indicator de
adres .

Adresa

MEMORIE
Locaia par
Locaia impar

0000

11110000

10101010

Variabila
. . . .

0002
......

11010100
1 1 0 0 10 1 0

10101110
1 1 0 0 10 1 0

. . . .
. . . .

0026

00000000

10000000

x = 128

0028

00000000

11110000

y = 240

0030
0032
0034

00000000
00000000
00000000

00000000
00000000
00000000

0036

11110000

00111111

0038

1 1 0 0 10 1 0

1 1 0 0 10 1 0

z = 1.0

. . . .

n tabelul de mai sus, este reprezentat o zon de memorie, n


care sunt stocate variabilele x, y, z, declarate astfel:
int
double

*px, *py, x=128, y=240 ;


*pz, z=1.0 ;

Se rezev doi octei pentru variabilele de tip ntreg i 8 octei (64


de bii) pentru variabila z, de tip real, dubl precizie.

Variabilele px, py, pz, sunt de tip pointer (adres); ele pot
furniza adresele variabilelor de tip corespunztor, dac sunt asociate
cu acestea prin atribuiri de forma:
px = &x; py = &y; pz = &z;
Din px = &x i din tabelul de mai sus, rezult px = 0026 ;
Analog, py = 0028, pz = 0030.
Observaii:
{ Variabilele de tip pointer se declar cu prefixul '*' : *p, *u, *v ;
{ Variabilele de tip pointer se asociaz cu adresa unui obiect prin
atribuiri de forma p = &var, unde var este o variabil declarat;
semnul '&' arat c se consider adresa lui var (care se transfer
n pointerul p);
{ O variabil de tip pointer trebuie declarat astfel:
<tip de baz> *<identificator> ;
unde tipul de baz reprezint tipul de date pentru care va memora
adrese.
Exemple:
float *a1, var_1; / /pointerul a1 va memora adrese pentru valori float
int *a2, var_2; // pointerul a2 va memora adrese pent ru valori int
a1 = &var_1
; // atribuire corect , var_1 este de tip float
a2 = &var_2
; // atribuire corect, var_2 este de tip int
a2 = &var_1
; // greit, pentru c a2 se asociaz cu variabile int
{ Operaia invers, de aflare a valorii din memorie, de la o anumit
adres, se realizeaz cu operatorul * pus n faa unui pointer:
int *p, x, y;
x = 128; p = &x;
// p = adresa lui x
y = *p;
// y = valoarea de la adresa p, deci y = 128
y = *&x; // y = valoarea de la adresa lui x, deci y = 128;
Aadar, operatorul * are efect invers fa de operatorul & i dac
se pun consecutiv, efectul este nul: x = *&x, sau p = &*p.
{ Un pointer poate fi fr tip , dac se declar cu void :
void *p ;
caz n care p poate fi asociat cu orice tip de variabile. Exemple:
int x;
float
y;
void *p;
. . . . . .
p = &x ; // atribuire corect, p este adresa lui x

p = &y ;

// atribuire corect, p este adresa lui y

{ O variabil pointer poate fi iniializat la fel ca oricare alt


variabil, cu condiia ca valoarea atribuit s fie o adres:
int k ;
// pointerul adr_k este egal cu adresa lui k
int *adr_k = &k ;
Un pointer nu poate fi iniializat cu o adres concret, deoarece
nu poate fi cunoscut adresa atunci cnd se scrie un program; adresa
este dat de sistemul de operare dup compilare, n funcie de zonele
libere de memorie, disponibile n momentul execuiei. Astfel, declaraii
de tipul:
p = 2000 ;
&x = 1200 ;
sunt eronate i semnalizate de compilator ca erori de sintax.
O expresie de tipul *<adresa> poate s apar i n stnga:
*adr_car = 'A' ; // caracterul A se memoreaz la adresa=
adr_car.
Exemple de programe care utilizeaz pointeri:
1)

#include <stdio.h>
main ( )
{
int x = 77 ;
printf("Valoarea lui x este: %d \n", x);
printf("Adresa lui x este: %p \n", &x);
}
Funcia printf() utilizeaz specificatorul de format %p, pentru
scrierea datelor de tip adres.
2)

#include <stdio.h>
main ( ) // afisarea unei adrese si a valorii din memorie
{
char *adr_car, car_1='A' ;
adr_car = car_1; // pointerul ia valoarea adresei lui car_1
printf("Valoarea adresei: %p \n", adr_car);
printf("Continutul de la adr_car %c \n", *adr_car);
}

3)

#include <stdio.h>
main ( ) // perechi de instruct iuni cu acelasi efect
int x=177, y, *p ;

y = x + 111;
// y = 177+111=288
p = &x; y = *p + 111; // y = 177+111=28 8, acelasi efect
x = y ;
// x = 288
p = &x; *p = y ; // x = 288, adic valoarea lui y
x++ ;
// x = x+1, deci x = 299
p = &x ; (*p)++ ; // x = 299+1=300 }

2.1.6. Tipul enumerativ


Se utilizeaz cnd o variabil trebuie s ia un numr redus de
valori, de natur nenumeric. Sintaxa conine cuvntul cheie enum :
enum

<identificator > {lista de valori} ;

unde identificatorul este numele tipului de date .


Exemple:
enum culoare {ALB, ROSU, GALBEN };
enum culoare fanion_1, fanion_2;
A doua declaraie definete dou variabile de tip "culoare",
fanion_1 i fanion_2.
Se pot nlocui cele dou declaraii, prin una singur:
enum culoare {ALB, ROSU, GALBEN } fanion_1, fanion_2 ;
Fiecare constant din list va primi o valoare ntreag dat de
poziia sa n list, 0, 1, 2, . . . , n. n exemplul de mai sus, ALB=0,
ROSU=1, GALBEN=2 .
Tipul enumerativ nu este, de fapt, un tip nou; este o nou alocare
de nume pentru tipul ntreg.
Este permis asocierea constantelor simbolice cu valori ntregi,
altele dect n asocierea implicit:
enum cod_err {ER1=-2, ER2, ER3=3, ER4, ER5} ;
Asocierea de valori se va face astfel:
ER1=-2, ER2=-1, ER3=3,
ER4=4,
ER5=5.
Se observ c pentru constantele neasociate cu valori la
declarare, se dau valori succesive n ordine cresctoare.
Tipul enumerativ produce economie de memorie, pentru c se
utilizeaz un singur octet, pentru o valoare din list.

2.2. Operatori i expresii

Un operator este o comand ce acioneaz asupra unor obiecte


(termeni) ce se numesc generic operanzi; fiecare operator este definit
printr-un simbol special sau grup de simboluri. Operatorul determin
executarea unei anumite prelucrri (operaii) asupra operanzilor.
O formul n care exist operanzi i operatori este numit
expresie .
Un operand poate fi: constant, variabil simpl, nume de funcie,
nume de tablou, nume de structur, expresie nchis ntre paranteze
rotunde. Orice operand are un tip i o valoare concret n momentul n
care se efectueaz operaia.
Operatorii se pot clasifica, dup numrul de operanzi pe care i
combin, n:
{ unari (acioneaz asupra unui singur operand);
{ binari ( acioneaz asupra a doi operanzi);
{ ternar (acioneaz asupra a trei operanzi) - exist doar unul n C.
Dup tipul operaiei ce se execut, operatorii sunt de mai multe
categorii: aritmetici, logici, relaionali, condiionali, de atribuire.
La evaluarea unei expresii, se ine seama de categoria de
prioritate a fiecrui operator, de asociativitatea operatorilor de
aceeai prioritate i de regula conversiilor implicite. n tabelul urmtor
sunt prezentai sintetic operatorii din limbajul C - standard, cu
principalele lor caracteristici.
Niv.

Categorie

1
Selectori

Operatori
( )
[ ]

->

.
!
~

+
++

Op. unari

-&

*
sizeof
(tip)
3

Op. aritm._1

Op. aritm._2

Deplasri

<< >>

Relaionali

< <= > >=

Egalitate

==

!=

Semnificaie

Asociere

Expresie, apel de funcie


Expresie cu indici
Selectori la structuri

stnga dreapta

Negare logic
Negare bit cu bit
Plus, minus (semne aritmetice)
Incrementare/ Decrementare
Luarea adresei
Indirectare
Dimensiune operand (n octei)
Conversie explicit de tip

dreapta stnga

nmulire, mprire, rest

st - dreapta

Adunare, scdere

st - dreapta

Deplasare stnga, dreapta

st - dreapta

mai mic/egal, mai mare/egal

st - dreapta

Egal logic, diferit de

st - dreapta

8
9
10

Operatori
logici

11
Op. condiional

14
Op. de
atribuire
15

i logic bit cu bit

st - dreapta

Sau exclusiv bit cu bit

st - dreapta

Sau logic bit cu bit

st - dreapta

i logic (ntre expresii)

st - dreapta

Sau logic (ntre expresii)

st - dreapta

(expresie)? expr_1 : expr_2

dreapta-st.

&&
||

12
13

&

Virgula

=
*= /= %= += -=
&=
^=
|=
<<= >>=
,

Atribuire simpl
dreapta Atribuire produs, ct, rest, sum, dif. stnga
Atribuire i, sau excl., sau bit cu bit
Atribuire i deplasare stg., dreapta
Evalueaz op1, op2. Valoarea expr. st - dreapta
este op2.

Nivelul de prioritate arat cum se evalueaz expresiile care


conin doi sau mai muli operatori, cnd nu exist paranteze care s
impun o anumit ordine a operaiilor.
Operatorii de nivel 1 au prioritate maxim; n tabel operatorii
sunt prezentai n ordinea descresctoare a prioritii. Operatorii cu
acelai nivel de prioritate se execut conform regulii de asociere:
stnga - dreapta sau dreapta - stnga. De exemplu, n expresia:
x= *p++ ;

apar doi operatori: cel de indirectare (derefereniere) i cel de post incrementare; se pune ntrebarea: ce se incrementeaz, p sau *p ?,
adic ce operator acioneaz primul ?. Din tabel, se vede c att ++
ct i * au acelai nivel de prioritate, dar ordinea de asociere este
dreapta-stnga, aadar, ++ i apoi *. Se incrementeaz aadar p nu *p.
Dac dorim s incrementm *p, trebuie scris:
x=(*p )++;

Asociativitatea operatorilor arat cum se evalueaz expresiile,


n care, apare acelai operator de mai multe ori. De exemplu, n
expresia:
c=x*y *z ;

apar dou nmuliri. Expresia se poate evalua ca (x*y)*z sau x*(y*z).


Operatorul * are asociativitatea de la stnga la dreapta, adic expresia
se interpreteaz
c=(x* y)*z ;

Operatorul de atribuire are asociativitatea de la dreapta la stnga,


deci expresia:
a=b=c =d;

se evalueaz ca i cum ar fi scris:


c=d;
b=c;
a=b;

Asociativitatea poate fi modificat prin paranteze rotunde,


ordinea de evaluare fiind "mai nti parantezele interioare ".
Operatorii unari (nivelul 2), cel condiional (nivelul 13) i cei de
atribuire (nivelul 14) se asociaz de la dreapta la stnga. Toi ceilali
operatori se asociaz de la stnga la dreapta.
2.2.1. Operatori aritmetici (+, -, *, /, %)
{ Se pot aplica numai la tipurile ntregi (char, short, int, long).
{ Operatorii + i - pot fi i unari, adic se poate scrie x=+2; y=-7;
{ Asociativitatea este de la dreapta la stnga pentru cei unari i de
la stnga la dreapta pentru cei binari.
{ Dac operanzii au tipuri diferite, se aplic conversii implicite de
tip, unul din operanzi fiind convertit la tipul celuilalt, rezultatul
fiind de tipul comun, dup conversie.
2.2.2. Operatori relaionali i de egalitate (< , <=, >, >=, ==, !=)
Toi sunt binari, iar expresiile formate se evalueaz la 0 sau la 1,
corespunztor valorilor logice fals i adevrat. n limbajul C nu exist
constante logice de tip TR UE i FAL SE , ntregii 1 i 0 fiind asociai cu
cele dou valori ( 1 = TR UE , 0 =F AL SE ). Mai general, orice valoare
numeric diferit de 0 este interpretat logic ca "adevrat". Operatorul
== produce valoarea 1 cnd cei doi operanzi sunt egali i 0 cnd sunt
diferii iar operatorul != , invers.
O eroare frecvent este confuzia dintre = (care este atribuire,
adic se transfer valoarea din dreapta, ctre variabila din stnga) i
== , adic testarea egalitii: x==y nseamn este x egal cu y ? (Da?
sau Nu?). Adesea, eroarea nu este semnalat de compilator, de
exemplu, dac n loc de :
if (x==2) . . .

se scrie
if (x=2) . . .

se atribuie lui x valoarea 2, care fiind diferit de 0 se interpreteaz ca


fiind "adevrat" i se execut istruciunea din ramura if, indiferent de
valoarea iniial a lui x (care se pierde). Astfel de erori, care nu pot fi
detectate de compilator sunt greu de remediat.
Asociativitatea este de la dreapta la stnga, iar prioritatea
operatorilor == i != este mai slab dect a celorlali operatori
relaionali.
O expresie de forma a <b = = x >y este echivalent cu ( a<b) = =
(x>y) .

2.2.3. Operatori logici (&& || !)


Operanzii sunt evaluai cu adevrat (diferit de 0) sau fals (egal
cu 0) iar valoarea expresiei este totdeauna 0 sau 1. Operatorul !
(negaie logic) transform o valoare 0 n 1 i o valoare diferit de 0
(adevrat ) n 0 (fals ).
Astfel, !0=1, !1=0, !7=0, !!7=1, !(2>3)=1.
La operatorii && (i logic) i || (sau logic), se garanteaz ordinea
de evaluare a operanzilor dup urmtoarele reguli:
{ la &&, se evalueaz nti primul operand; dac acesta este diferit
de 0, se evalueaz i al doilea operand iar valoarea expresiei este
dat de al doilea; dac primul operand este 0, nu se mai evalueaz
al doilea, deoarece orice valoare ar avea, n final se obine 0.
{ la || , se evalueaz, de asemenea, primul operand; dac valoarea
logic este 1 (adevrat) nu se mai evalueaz al doilea, deoarece
orice valoare ar avea, n final se obine 1 (adevrat).
Acest mod de evaluare este eficient, deoarece se reduce la
minimum posibil numrul de operaii, ceea ce nseamn economie de
timp.
Prioritatea operatorului unar ! este mai mare dect a tuturor
operatorilor aritmetici, logici i relaionali iar a operatorilor && i ||
este inferioar operatorilor relaionali, adic o expresie de forma:
a>b&& c==d && e! =f

se evalueaz ca i cum ar fi scris:


(a>b) &&(c == d) && (e != f)

Prioritatea operatorului || (sau) este mai sczut dect a lui &&


(i) iar asociativitatea este de la stnga la dreapta.
2.2.4. Opratori de incrementare / decrementare (++ --)

Au ca efect creterea valorii cu 1 (incrementare) sau scderea


valorii cu 1 (decrementare). Fiecare operator are dou forme: forma
prefixat, adic ++x, --y, sau forma postfixat x++ , y--. Valoarea
expresiei x++ este valoarea lui x nainte de incrementare iar valoarea
expresiei ++x este valoarea lui x dup incrementare. Aceti operatori
nu se aplic unor expresii aritmetice i nici constantelor. Exemple:
n=7; m=n++ ; //se atrib uie lui m valoarea 7
n=7; m=++n ; //se atrib uie lui m valoarea 8

ambele variante modific n final valoarea lui n n n+1.


Operatorii de incrementare / decrementare contribuie la scrierea
unor programe mai eficiente i mai scurte.
2.2.5. Operatorul unar sizeof
Returneaz dimensiunea n octei (numrul de octei ocupai) a
unei variabile, a unui tip de date, sau a unui tip structurat. De exemplu,
siz eo f( in t) d numrul de octei pe care este memorat un ntreg iar
dac tab este un tablou, expresia:
sizeo f(ta b) / sizeo f( ta b[ 0] )

d numrul de elemente ale tabloului, oricare ar fi tipul de date, pentru


c se mparte numrul total de octei ocupat de tablou la numrul de
octei ocupai de un element.
n diverse implementri mai vechi, sizeof ntorcea tipuri diferite
de ntregi, de exemplu int, unsigned int sau unsigned long. Standardul
ANSI introduce un tip predefinit, numit size_t, garantnd c sizeof
ntoarce acest tip de ntreg. Astfel, programele care folosesc acest tip,
capt o portabilitate sporit. Tipul size_t este definit n fiierul
header stddef.h , ca i n stdio.h i n alte fiiere header uzuale.
2.2.6. Operatori de atribuire (= *= += /= %= -= &= ^= |= <<= >>=)
n limbajul C, pe lng operatorul de atribuire normal = (se
atribuie valoarea expresiei din dreapta, variabilei din partea stng a
semnului =), exist i operatori de atribuire combinat cu o operaie,
pentru a face textul mai concis, dat fiind c foarte frecvent atribuirea
este nsoit de o operaie. Astfel, avem scrieri echivalente:
x=x+3 ;
y=y*6 .5;
u=u/( v-2) ;
a=a % b ;

echival en t
echival en t
echival en t
echival en t

cu
cu
cu
cu

x
y
u
a

+=
*=
/=
%=

3;
6.5;
v-2;
b;

Tipul expresiilor formate cu operatorii de atribuire este tipul


expresiei din stnga. Valoarea unei expresii de atribuire de forma a=b
este valoarea membrului drept, dup o eventual conversie la tipul
membrului stng. Asociativitatea este de la dreapta la stnga; de
exemplu, n atribirea multipl de mai jos:
x = y = z = 0 ;

ordinea operaiilor este:


x = (y = (z = 0));

faptul c z = 0 este o expresie cu valoarea 0 (a membrului drept), face


posibil atribuirea urmtoare y = 0 i apoi x = 0; aadar, n final toate
cele trei variabile primesc valoarea 0.
Evaluarea expresiilor cu operatori compui de atribuire se face pe
baza definiiei echivalente a acestora. De exemplu, expresia:
a = b += c + 1;

se evalueaz ( de la dreapta la stnga) ca:


a = (b = b + (c+1)) ;

2.2.7. Operatorul condiional (? :)


n situaia n care unei variabile trebuie s i se atribuie o valoare
sau alta n funcie de o condiie logic, se poate folosi instruciunea if
dar n C exist, ca variant, operatorul condiional (? :), cu sintaxa:
(expr ) ? expr_ 1: expr_ 2;

unde (expr) este evaluat logic; dac este adevrat (diferit de 0), se
evalueaz expr_1 care devine valoare final pentru expresia
condiional; dac (expr) este fals (egal cu 0), atunci este evaluat
expr_2, care devine valoare final. Se garanteaz c una i numai una
dintre expr_1 i expr_2 este evaluat.
Exemplu: tiprirea unui vector numeric, cu un anumit numr de
elemente pe linie (de exemplu, 10), separate prin blank:
for(i =0 ; i<n; i++)
print f("% 6d %c ", a[ i] ,( i% 10 == 9 || i==n-1 ) ? '\n': '
');

Se tiprete ntregul a[i] i apoi un singur caracter (cu formatul


%c), acesta fiind '\n' sau ' ' dup cum s-a tiprit tot al 10-lea ntreg sau
ultimul. Orice alt variant de program ar fi dus la mai multe
instruciuni.
2.2.8. Operatorul virgul ( , )
O expresie n care apare operatorul virgul are forma:
expr_ 1, expr_ 2

Evaluarea se face n ordinea (garantat) expr_1 i apoi expr_2.


Valoarea final este dat de expr_2. Se aplic regulile privind
conversia de tip (dac este cazul).
O asemenea construcie este util acolo unde sintaxa impune o
singur expresie. Prin folosirea operatorului virgul, putem realiza mai
multe aciuni.

2.3. Tipuri structurate: tablouri i structuri


2.3.1. Tablouri
Un tablou este o colecie de date de acelai tip - numit tip de
baz - la care accesul se realizeaz prin intermediul indicilor.
Elementele unui tablou ocup, n memorie, locaii succesive iar ordinea
n care sunt memorate elementele, este dat de indici.
O caracteristic important a unui tablou este dimensiunea, adic
numrul de indici. Astfel, exist tablouri:
{ unidimensionale - elementele au un singur indice a[0], a[1], . . .;
{ bidimensionale - elementele au doi indici b[0][0], b[0][1], . . . ;
{ multidimensionale - elementele au mai muli indici.
Declaraia de tablou, n forma cea mai simpl, conine tipul
comun al elementelor sale, numele tabloului i limitele superioare
pentru fiecare indice (valorile maxime ale fiecrui indice):
<tip>

<nume> [dim_ 1] [d im _2 ]. . . [dim_n ];

{ tip este cuvnt cheie pentru tipurile predefinite;


{ nume este un identificator pentru tablou;
{ di m_ k este limita superioar pentru indicele k ; acesta va lua
valorile
0, 1, 2, . . . k-1, n total k valori.

Limitele d im _k sunt expresii constante, adic expresii care pot fi


evaluate la compilare. Numele unui tablou este un simbol care are ca
valoare adresa primului element din tablou.
La ntlnirea unei declaraii de tablou, compilatorul aloc o zon
de memorie pentru pstrarea valorilor elementelor sale. Numele
tabloului respectiv poate fi utilizat n diferite expresii, n program,
valoarea lui fiind chiar adresa de nceput a zonei de memorie care i-a
fost alocat.
Exemple:
int a[20];
char sir[8 0] ;
float x[10][5 0] ;

Declaraiile de mai sus arat c a este un tablou de 20 de


variabile de tip ntreg, sir este un tablou de 80 de caractere iar x este
un tablou bidimensional care conine 10 x 50 = 500 elemente de tip
real.
Elementele tabloului pot fi accesate prin numele tabloului urmat
de o expresie ntreag pus ntre paranteze drepte. Expresia trebuie s
ia valori n domeniul 0, . . . , dim-1. De exemplu, referiri corecte la
elementele tabloului a de mai sus sunt: a [0 ], a[ 1], . . . , a[ 19 ]
iar la elementele tabloului x sunt:
x[0] [0 ], x [0 ][ 1] ,. .. ,
x[9][ 0] , x[9][1], .. ., x[9][ 49].

Prelucrrile de tablori se implementeaz de obicei prin cicluri


for, deoarece numrul de repetri este cunoscut, fiind dependent de
valorile indicilor.
Tablourile pot fi declarate n interiorul funciilor, caz n care sunt
implicit n clasa auto, sau n exteriorul funciilor, caz n care sunt
implicit n clasa extern.
Tablourile nu pot fi declarate n clasa register .
Tablourile pot fi iniializate cu valori. Iniializarea respect
regulile care deriv din clasa de alocare: un tablou n clasa auto
(iniializat explicit) va fi iniializat la fiecare intrare n funcia
respectiv iar un tablou n clasa static sau extern (iniializat explicit)
se iniializeaz o singur dat, la ncrcarea programului n memorie.
Tablorile n clasa static sau extern, care nu sunt iniializate explicit,
se iniializeaz automat cu 0 iar cele n clasa auto vor primi valori
aleatoare.
Iniializarea se face cu semnul egal i o list de valori:
int a[4]={ 2, 4, 6, 8};
char s[4]= {' x' , 'y', 'z', '\0'};

Dac lista de valori conine mai puine elemente dect


dimensiunea declarat a tabloului, celelalte valori se iniializeaz cu 0,
indiferent de clasa de alocare. De exemplu, dup declaraia:
int a[7]={ 1, 5, 11, -3};

elementele a[4], a[5], a[6] vor conine valoarea 0.


n cazul unui tablou iniializat, se poate omite dimensiunea, ea
fiind calculat de compilator, dup numrul de elemente din list.
De exemplu, declaraia :
float x[ ]={1.0 , 2.1, 3.2};

arat c x este un tablou cu trei elemente reale, specificate n list.


Un caz special l reprezint tablourile de caractere, care pot fi
iniializate i cu iruri constante de caractere. Astfel, declaraia:
char s[ ]="ab cd ef " ;

este perfect echivalent cu


char s[ ]={'a ', 'b ','c ', 'd ', 'e ', 'f', '\ 0' };

observnd prezena caracterului special '\0' (octetul nul, care are rol de
terminator de ir).
Transmiterea tablourilor cu o dimensiune la funcii se face prin
declararea parametrului formal al funciei ca fiind tablou, ceea ce se
realizeaz punnd paranteze drepte. Nu este necesar prezena
explicit a dimensiunii tabloului ntre paranteze, deoarece ceea ce se
transmite efectiv este adresa de nceput a tabloului.
Dac totui, este necesar dimensiunea tabloului, aceasta se
poate preciza ntr-un parametru separat. n exemplul de mai jos, este
prezentat o funcie care calculeaz produsul scalar a doi vectori de
lungime n, reprezentai prin dou tablouri de numere reale:
float prod_s ca la r( floa t x[], float y[], int n)
{
float prod=0.0 ;
int i;
for (i=0; i<n; i++)
prod +=x[i ]* y[i] ;
retur n prod;
}

Dac sunt declarate dou asemenea tablouri, de 3 elemente:


float a[3]= {- 1. 0, 2.0, 3.14};
float b[3]= {0 .9 , -1.02, 0.0};
float ab;

atunci produsul scalar al vectorilor a i b se poate obine prin:


ab=pr od_s ca la r( a, b, 3);

Tablourile de caractere se prelucreaz diferit, deoarece ele nu au


de obicei specificat dimensiunea; aceasta se deduce din prezena
terminatorului '\0' care este ultimul caracter din ir.
O funcie care calculeaz numrul de caractere dintr-un asemenea
tablou este:
int nr_car (c ha r s[])
{
int i;
for (i=0; s[i] != '\0'; i++);
retur n i;
}

Se observ c terminatorul '\0' nu se consider caracter util, deci


nu este inclus n rezultat. Funcia de mai sus este echivalent cu
funcia de bibliotec strlen() .
Pentru tablouri uni- sau multi-dimensionale, alocarea memoriei se
face dup reguli stricte, cuprinse n funcia de alocare a memoriei .
Modul de alocare a memoriei se poate ine minte prin regula
sintetic: "tabloul este alocat la adrese succesive de memorie, ultimul
indice avnd variaia cea mai rapid". De exemplu, considernd
declaraia:
char c[2][2][3]
imaginea n memorie a elementelor tabloului c[] va fi:
c[0][ 0][0 ]
c[0][ 0][1 ]
c[0][ 0][2 ]
c[0][ 1][0 ]
c[0][ 1][1 ]
c[0][ 1][2 ]
c[1][ 0][0 ]
c[1][ 0][1 ]
c[1][ 0][2 ]
c[1][ 1][0 ]
c[1][ 1][1 ]
c[1][ 1][2 ]

Adresa lui c[1][0][1] se calculeaz astfel:


adr(c[1][0][1]) = baza(c) + 1*2*3 + 0*3 + 1 = 7,
elementul c[1][0][1] gsindu-se ntr-adevr la 7 octei distan de
c[0][0][0]. Adesa se calculeaz astfel:
baza(tab) + nr.octei/element * [ indice_1 * dim_2 * dim_3 +
indice_2 * dim_3 +
indice_3 ].
n cazul tablourilor cu dou dimensiuni, regula de alocare este
dat de: "matricele se memoreaz pe linii"; ntr-adevr, un tablou
x[2][3] va fi memorat astfel:
x[0][ 0]

x[0][ 1]
x[0][ 2]
x[1][ 0]
x[1][ 1]
x[1][ 2]

n limbajul C, un tablou cu mai multe dimensiuni este tratat ca un


tablou cu o dimensiune (i anume, prima), care are ca elemente tablouri
cu restul de dimensiuni; astfel, nu exist limitri sintactice ale
numrului de dimensiuni. De exemplu tabloul:
int a[2][3 ];

este interpretat ca un tablou uni-dimensional cu 2 elemente, fiecare


fiind tablou 1-dimensional cu 3 elemente: a[0] este tablou cu 3
elemente, a[1] este tablou cu 3 elemente.
Transmiterea unui tablou cu mai multe dimensiuni unei
funcii
n toate situaiile, n care lucrm cu tablouri cu mai multe
dimensiuni, trebuie s inem seama de funcia de alocare a tabloului i
s stabilim dac la compilare se calculeaz corect adresa elementelor.
Este nevoie de toate dimensiunile, mai puin prima.
S considerm o funcie care tiprete elementele unui tablou.
Ceea ce se transmite funciei este adresa de nceput dar sunt necesare
i informaiile pentru calculul adresei, adic dimensiunea a doua.
Definim tipul de date DATA i formatul corespunztor de tiprire:
typed ef int DATA;
#defi ne FORMA T
"%7d"

Este incorect forma:


void matpr in t( DA TA a[][] , int m, int n)
{
int i, j;
for (i=0; i<m; i++) {
for (j=0; j<n; j++)
printf( FO RM AT , a[i][ j] );
print f( "\ n" );
}
}

deoarece nu se poate calcula adresa lui a[i][j].


n momentul compilrii, nu este cunoscut a doua dimensiune a
lui a, deci apare eroare, chiar la compilare.
Pentru tiprirea unei matrice declarate cu:
DATA x[3][4 ];

prima linie din definiia funciei trebuie s fie:


void matpr in t( DA TA a[][4 ], int m, int n )

iar apelul va fi:

matpr int( x, 3, 4);

Acum compilatorul "tie" s calculeze adresa lui a[i][j], dar


funcia astfel construit poate fi folosit numai cu matrice cu 4
coloane. O soluie mai eficient este folosirea unei forme echivalente
pentru a[i][j], anume aceea care simuleaz funcia de alocare memorie,
pe baza celei de-a doua dimensiuni, care este n.
void matpr in t( DA TA (*a)[ ], int m, int n)
{
int i, j;
for (i=0; i<m; i++) {
for (j=0; j<n; j++)
print f( FO RM AT, ((DATA *)a)[i* n+ j] ;
print f( "\ n");
}
}

Cea ce se transmite este adresa de nceput a tabloului a, vzut


ca pointer la un tablou cu o dimensiune de tipul DATA (nu mai apar
erori la compilare). Conversia explicit de tip ((DATA *)a) face ca a
s fie vzut ca un pointer la DATA, deci ca un tablou uni-dimensional.
Indicele i*n+j face ca s fie accesat corect elementul a[i][j], indiferent
de dimensiuni.
n concluzie, se poate omite numai prima dimensiune a tabloului,
celelalte trebuie cunoscute, pentru a se cunoate modul de organizare;
dimensiunea omis se poate transmite ca parametru separat, ea trebuie
cunoscut la execuia funciei; programul urmtor calculeaz suma
elementelor unei matrice.
#incl ude <stdio .h >
#incl ude <conio .h >
#defi ne MAX 3
int suma(i nt tab[][MA X] , int r);
void afis( in t tab[] [M AX ], in t r);
void main()
{
int mat[3] [M AX ]= {{ 1,2, 3} ,{ 4, 5, 6} ,{7, 8, 9} };
//ini tial iz ar e matrice
int rind=3 ;
afis( mat, ri nd );
print f("\ n Suma este %d",s um a( ma t,ri nd )) ;
getch ();
}
int suma(i nt tab[][MA X] ,i nt r)
{
int i, j, s=0;

for(i =0;i <r ;i ++ )


for(j= 0; j< MA X; j++)
s+=tab [i ][ j] ;
retur n s;
}
void afis( in t tab[] [M AX ], int r)
{
int i,j;
print f("\ n" );
for(i =0;i <r ;i ++ )
{
for(j =0 ;j <M AX ;j++ )
print f( "
%2d", ta b[ i] [j]) ;
print f( "\ n" );
}
}

2.3.2. Structuri
n multe aplicaii, este necesar gruparea datelor sub forma unei
mulimi de elemente care s poat fi prelucrate att element cu element
ct i global, ca mulime. Dac toate elementele sunt de acelai tip,
mulimea de elemente poate fi definit ca tablou; dac elementele sunt
diferite ca tip, atunci mulimea lor se definete ca structur .
Structura este o colecie de date de tipuri diferite, grupate sub
acelai nume. Un exemplu simplu de structur este data calendaristic;
ea conine 3 componente: zi (1, 2, . . . , 31) - de tip ntreg, luna - de
tip ir de caractere i anul - de tip ntreg.
Pentru a utiliza structuri n programe, acestea trebuie definite.
Sintaxa definirii unei structuri este:
struc t <nume >{
declara ti i de varia bi le ;
}
unde <n ume> este un identificator de tip de date definite de utilizator,
str uc t este cuvnt cheie iar declaraiile de variabile sunt cele

obinuite.
Dup o asemenea definiie, se pot declara variabile sau funcii de
tipul <nume> . Exemple:
struc t compl ex {f lo at re; float im};
struc t stude nt {c ha r nume[2 5] ;i nt gr;fl oa t
medie _a n} ;

Aceste declaraii definesc numai structurile, fr a aloca


memorie, deoarece nu au fost nc declarate variabile - structur.
Pentru a defini obiecte concrete de tip structur, cu rezervare de
memorie, se scrie:
struc t compl ex z1, z2, zeta;
struc t stude nt std[99] ;

Variabilele z 1, z 2, ze ta sunt de tip "complex", deci fiecare va


avea dou componente reale, re i i m . Cele 100 de variabile s td [k ]
sunt de tip "student", fiecare cu 3 componente: n um e, gr ,
medie _a n.

Definirea structurii i declararea variabilelor de tip structur pot


fi combinate ca mai jos:
struc t compl ex {f lo at re; float im} z1, z2, zeta;

situaie n care numele structurii ("complex") ar putea lipsi, dac


definirea conine i declaraii de variabile:
struc t {floa t re; float im} z1, z2, zeta;

dar acest mod de lucru nu se recomand, mai ales cnd se lucreaz cu


mai multe tipuri de structuri. Este recomandabil ca structurile s aib
asociate nume pentru identificarea tipurilor lor i s se foloseasc
declaraia typedef pentru a lucra mai comod:
struc t compl ex {f lo at re; float im};
typed ef struc t comple x COMPLE X;
COMPL EX z1, z2, zeta;

sau prin combinarea lui typedef cu definiia structurii:


typed ef struc t {float re; float im} COMPLE X;
COMPL EX z1, z2, zeta;

n concluzie, o structur asociaz diverse tipuri de variabile,


simple sau structurate, sub un singur tip de date. ntregul grup poate fi,
apoi, tratat global (prin atribuiri, comparri, transmitere la funcii
etc.).
Iniializarea structurilor. Se poate face la declarare, prin
precizarea unor valori pentru variabilele componente, de exemplu:
COMPL EX
zeta = {1.0, -2.33 };
struc t stude nt sx = {"Tana se Ion", 8310, 9.33};

sau, dac structura conine date mai complexe (tablouri, alte structuri),
se pot folosi mai multe niveluri de acolade:
struc t tty {int a[10]; int b[3]; char *s};
struc t tty
x =
{

{2, 2, 3, 3, 10, 20},


{22, 44, 66},
"Cheia de cripta re, codata "
};

Se iniializeaz primele 6 elemente ale tabloului a (celelalte se


vor iniializa automat cu 0), toate cele 3 elemente ale tabloului b i
irul de caractere s.
Accesul la componentele unei structuri. Se face prin operatorul
"." aplicat structurii, conform sintaxei:
<nume variab il a> .< nume compo ne nt a>

Exemple:
COMPL EX z, zeta;
struc t stude nt sx;
z.re= 2.0;
z.im= 7.2;
zeta= z;// ec hi va le nt cu zeta.r e= z.re ; zeta.i m= z.im ;
sx.nu me=" Ni cu le sc u D. R. Liviu" ;
sx.gr = 8315;
sx.me die_ an = 8.99;

Este posibil ca o structur s conin o alt structur drept


membru; n exemplul urmtor, se definete structura PE RSOA NA , care
are o component a dr (adresa persoanei), aceasta fiind la rndul su o
structur cu trei componente: oras, strada , cod .

typed ef struc t

{ char
char
long
{ char

*oras ;
*stra da ;
cod; } ADRES A;
*nume; ADRESA adr;}

typed ef struc t
PERSO AN A;
PERSO ANA zz;
zz.nu me=" Gh eo rg he Hagi";
zz.ad r.or as =" Co ns ta nt a" ;
zz.ad r.st ra da =" B- du l Tomis 44";
zz.ad r.co d= 55 67 ;

Dac elementele sunt tipuri structurate (tablouri, iruri etc.),


accesul se face n mod natural: x .num e este un pointer la caracter iar
x.num e[ i] reprezint elementul i al irului x.num e .

Pointeri la structuri i accesul prin pointeri. Un pointer ctre o


structur se declar similar cu pointerii ctre variabile simple:
COMPL EX

z, *pz;

unde *pz este un pointer ctre o structur de tipul complex; se


poate face acum atribuirea:
pz=&z ;

prin care se d lui pz adresa structurii z. n operaiile cu pointeri


la structuri (comparaii, atribuiri), acetia trebuie s fie definii ctre
acelai tip de structur (acelai nume de structur). Chiar dac dou
structuri sunt identice ca numr i tip de componente dar au nume
diferite, pointerii corespunztori nu sunt compatibili. Exemplu:
struc t tip_a {int x; float y} s1, *p1;
struc t tip_b {int x; float y} s2, *p2;
p1=&s 2; // eroare , adrese le sunt incomp at ib ile

Dac p este un pointer ctre o structur, atunci *p este structura


iar (*p).nume este un membru al structurii. S considerm din nou
structura student, anterior definit, variabila sx de tip student i un
pointer ps ctre o structur student, iniializat cu adresa lu sx; se pot
face atribuirile de mai jos:
struc t stude nt sx, *ps=& sx ;
(*ps) .num e= "I on es cu Octav ia n" ;
(*ps) .gr= 44 2;
(*ps) .med ie _a n= 9.73;

Limbajul C permite o form echivalent


pentru
aceste
construcii: dac p este un pointer ctre o structur iar ms este un
membru al acestei structuri, atunci ( *p). ms se poate scrie echivalent
p-> ms , unde simbolul - > este format din caracterul - (minus) urmat de
caracterul > (mai mare). Cele trei atribuiri de mai sus, se pot scrie,
aadar:
ps->n ume= "I on es cu Octavi an ";
ps->g r=44 2;
ps->m edie _a n= 9.73;

Se pot folosi operatorii ++ / -- pentru p sau ms:

(++p) ->ms ; //incr emen te az a p si apoi acces ea za pe


ms
(p++) ->ms ; //acce seaz a ms si apoi incre me nt eaza p
(p->m s)++ ; // incre me nt ea za (p->ms), dupa acces
++(p- >ms) ; // incre me nt ea za (p->ms), in ai nt e de
acces

Toate regulile referitoare la pointeri ctre variabile simple rmn


valabile i la pointeri ctre structuri.

3 Instruciuni

Instruciuni simple

Instruciuni structurate

{ de atribuire

{ instr. compus

{ { . . . }

{ break

{ alternative

{ if

{ continue
{ goto

{ switch
{ repetitive

{ return

{ while
{ do while
{ for

Descrierea unui proces de calcul se realizeaz prin instruciuni.


Instruciunile simple sunt auxiliare, fiind utilizate n componena
celor structurate.
Orice proces, poate fi descris prin trei structuri de control
fundamentale: secvenial, alternativ i repetitiv.
Structura
secvenial
elementar
este
reprezentat
de
instruciunea compus.

3.1. Instruciunea compus


Instruciunea compus este o succesiune de instruciuni incluse
ntre paranteze acolade, care poate fi precedat de declaraii.
Dac sunt prezente declaraii, atunci variabilele definite sunt
valabile doar n timpul execuiei instruciunii compuse.
Instruciunea compus este tratat sintactic ca o singur
instruciune; de aceea, se utilizeaz cnd sintaxa permite existena unei
singure instruciuni: n structura if, for etc.
Exemplu: schimbarea reciproc a valorilor ntre dou variabile, a,
b:
// instructiune compusa
{
int t ;
// variabila ajutatoare
t=a ;
a=b ;
b=t ;
}
Dup execuia secvenei de instruciuni, variabila t nu mai este
stocat n memorie i deci devine inexistent.

Partea executabil a unei unei funcii (corpul funciei) este de


fapt o instruciune compus.

3.2. Instruciunea if
Corespunde structurii alternative cu dou ramuri, sau unui bloc
de decizie cu dou ieiri. Sintaxa instruciunii if este:
if (expresie)
instructiune_1 ;
[else
instructiune_2 ; ]
Ramura else, inclus ntre [ ], este opional; instructiune_1 i
instructiune_2 pot fi oricare instruciuni ale limbajului, simple sau
structurate, inclusiv if. Dac este necesar, una sau ambele pot fi
nlocuite cu instruciuni compuse.
Efectul :
se
evalueaz expresia (se calculeaz valoarea expresiei) ;
{
{ dac este adevrat (valoare diferit de zero), se execut numai
instructiune_1 (instructiune_2 nu este luat n consideraie);
{ dac este fals (valoare = 0), se execut numai instruciune_2;
dac aceasta lipsete, se trece la instruciunea urmtoare din
program.
Observaii:
{ n C, valoarea logic "adevrat" este asociat cu " ! 0" iar
valoarea logic "fals" este asociat cu "= 0".
Valoarea expresiei poate fi de tip ntreg:
if (k) x=k+3 ; // pentru k<>0, se executa x=k+3
if (k=5) x=k+3 ; // se executa totdeauna x=k+3, pentru ca expr.=5
if (k==5) x=k+5 ; // se executa x=k+3, numai pentru k=5
{ Dac una sau ambele instruciuni din if, sunt tot instruciuni if,
else se asociaz cu cea mai apropiat instruciune if anterioar,
din acelai bloc, care nu are ramur else . Exemplu:
if (y==1)
if (a==2)
x=0;
else x=2;
Ramura else aparine lui if (a==2), cea mai apropiat
instruciune if anterioar, care nu are ramur else. Dac se dorete alt
apartene, se pote utiliza o instruciune compus:

if (y==1)
{
if (a==2)
x=0;
}
else x=2; // else apartine lui if (y==1)
3x 2+1 1
1. Se consider funcia f : R d R, f(x ) = x2+1 , x ! 0 .
3/2 , x = 0
S se afieze valoarea funciei, pentru o valoare a lui x introdus
de la tastatur.
#include <stdio.h>
#include <math.h>
int main(void) {
double x, f ;
printf("Introduceti valoarea lui x: ");
scanf("% lf ", &x); //t otdeauna, argumentul lui scanf este adresa:
&x

if (x !=0)
f=(sqrt(3*x*x+1)-1)/(x*x+1);
// instr_1
else
f=3.0/2.0 ;
// instr_2
printf("Valoarea functiei este: %lf \n", f);
return 0;
}
2. Se consider trei valori ntregi; s se determine cea mai mare.
Se utilizeaz instruciunea if pe mai multe niveluri.
a>b
b>c
return c

a>c
return b

return c

return a

if (a>b)
if (a>c) return a ; // intoarce ca rezultat valoarea a
else
return c ;
else
if (b>c) return b ;
else
return c ;

Se observ alinierea fiecrui else cu if -ul cruia i aparine.

3.3. Instruciunea switch


Permite realizarea structurii selective cu mai multe ramuri. Este o
generalizare a structurii alternative de tip if i ca urmare poate fi
realizat cu mai multe instruciuni if incluse unele n altele, pe mai
multe niveluri.
Utilizarea instruciunii switch, face ca programul s fie mai clar
dect dac se utilizeaz if pe mai multe niveluri.
Sintaxa instruciunii switch:
switch (expresie)
{
case const_1: instructiune_1;
instructiune_2;
. . . . . . . . . . ;
break ;
case const_2: instructiune_1;
instructiune_2;
. . . . . . . . . . ;
break ;
. . . . . . . . . . . . . . . . . . . . . . . .
case const_n: instructiune_1;
instructiune_2;
. . . . . . . . . . ;
break ;
[ default:
instructiune_1; // ramura optionala
instructiune_2;
. . . . . . . . . . ; ]
}
Efectul instruciunii switch este descris mai jos:
Se
evalueaz expresie (de tip ntreg) i se compar cu valorile
{
constante (care trebuie s fie distincte), din lista de etichete case;
{ Dac valoarea expresiei coincide cu una din constantele const_k,
se execut numai secvena de instruciuni corespunztoare acestei
etichete, pn la break , goto sau return i se trece la
instruciunea urmtoare din program, aflat dup paranteza

acolad }; n cazul n care secvenele de instruciuni nu conin


break, goto sau return, se execut secvena cazului, precum i
toate secvenele care urmeaz pn la sfritul lui switch ;
{ Dac valoarea expresiei nu coincide cu nici una din constantele
notate const_1, const_2, . . . , const_n, se execut succesiunea
de instruciuni aflat dup default (dac exist) i se trece la
instruciunea urmtoare din program, aflat dup paranteza
acolad }.
{ Un caz (case) poate conine una, mai multe sau nici o instruciune,
adic pot exista mai multe cazuri asociate la aceeai secven de
instruciuni.
Exemple:
switch (c=getch( )) {
case 'd':
case 'D': del_file; break;
case 'l':
case 'L': list_file; break;
default: error( ); break;
}
Se citete un caracter de la consol (cu getch()) i se execut
una din funcile del_file(), list_file() sau error(), dup cum caracterul
este 'd' sau 'D', respectiv 'l' sau 'L', respectiv diferit de cele patru
caractere.
Comanda break asigur ieirea din switch dup execuia unei
funcii.
Comanda break de la default nu este necesar, dar este o bun
msur de protecie, n cazul n care se adaug ulterior alte cazuri,
dup default .
double calcul (doble op1, char operator, double op2)
{
switch (operator)
{
case '+' : return op1+op2;
case '-' : return op1-op2;
case '*': return op1*op2;
case '/': return op1/op2;
default: printf("Operator necunoscut\n");
exit(1);
}

}
Funcia calcul(), de mai sus, se apeleaz cu trei argumente: op1,
op2 i un operator (+, -, *, /); ea returneaz rezultatul operaiei, n
funcie de operatorul de tip caracter, care a fost specificat n lista de
argumente.
Funcia exit(1) produce terminarea execuiei programului; dac
valoarea specificat ca parametru este zero, arat terminare normal,
n caz contrar definete o terminare anormal, deci prezena unei erori.
n cazul de mai sus, se produce o terminare forat, din cauza unei
erori de operator. Funcia calcul(), n mod normal, returneaz o
valoare real de tip double. Din cauza operatorului necunoscut, funcia
nu poate returna o valoare normal, fapt ce necesit terminarea forat
a programului, altfel eroarea va genera un lan de alte erori, foarte greu
de depistat.
Funcia exit() este o funcie de bibliotec.
Pentru utilizarea n programe trebuie specificate fiierele header
<stdlib.h> sau < process.h> .

3.4. Instruciunea while


Corespunde structurii repetitive condiionate anterior. Sintaxa:
while (expresie)
instruciune ;
Efectul instruciunii while :
{ 1. Se evalueaz expresia;
{ 2. Dac are valoarea "adevrat" (este diferit de zero), se execut
instruciunea i se repet pasul 1.
{ 3. Dac are valoarea "fals" (este egal cu zero), execuia
instruciunii while se ncheie i se trece la instruciunea urmtoare
din program.
Pe poziia "instruciune" este acceptat o singur instruciune C;
dac sunt necesare mai multe, acestea se includ ntre paranteze acolade
i formeaz astfel o singur instruciune, compus.
Observaii:

{ Dac la prima evaluare, expresie=0, while se ncheie fr s se


execute "instruciune".
{ Dac n partea executabil apar instruciuni ca break, goto sau
return, se schimb aciunea normal a lui while, prin ncheierea ei
nainte de termen.
{ Instruciunea while se ncheie dac dup expresie se pune ';'
while (k<=100)
; // eroare: corpul lui while este vid
k=k+1
; // se considera "instructiune urmatoare"
{ Execuia instruciunii while trebuie s produc modificarea valorii
"expresie" dup un numr finit de repetri; n caz contrar, se
produce un ciclu infinit .
Exemplul 1: Tabelarea valorilor unui polinom:
# include <stdio.h>
main()
/ *afiseaza valorile lui p(x), pentru x=1, 2, . . .,10
p(x)=x*x - 3*x+2 */

{
int x=1;
while (x<=10) {
printf("x = %d \t p(x) = %d \n",x, x*x - 3*x +2);
x++ ;
}
}
Exemplul 2: Tabelarea valorilor funciei sinus(). Funcia sinus
este o funcie standard, se afl n fiierul math.h i are prototipul
double sin(double x);
Argumentul x este interpretat n radiani; pentru a utiliza n
program valori ale lui x n grade, acestea trebuie nmulite cu factorul
f=PI/180.
# include <stdio.h>
# include <math.h>
# define PI 3.14159 2653 5897 9
main()
{
int x=0 ;
double f=PI/180. 0 ;
while (x<=360) {
printf( "sin (%d) = %.16f\n",x ,sin (x*f ));
x++ ;

}
}
Valorile se afieaz cte una pe linie, deci programul afieaz
360 de linii. Pe ecran pot fi analizate doar ultimele 25 de linii. n
asemenea situaii se pune problema de a ntrerupe temporar execuia
programului, dup ce s-au afiat 25 de linii. Oprirea programului se
poate face n mai multe moduri; un mod simplu de oprire este apelarea
funciei getch() n momentul n care ecranul este plin. Apelarea funciei
getch() oprete execuia, deoarece se ateapt acionarea unei taste i
se afieaz fereastra utilizator. La acionarea unei taste, programul
intr din nou n execuie.
n exemplul urmtor se apeleaz funcia getch() dup afiarea a
23 de valori (linii); pe linia 24 se va afia textul:
Daca doriti sa continua ti, apasati o tasta!
Se utilizeaz valorile lui x =0, 1, . . . , 360, pentru a apela getch,
cnd x ia valorile 23, 46, 69, . . . , deci multiplu de 23. Aceasta se
exprim prin expresia x % 23 ==0.
# include <stdio.h>
# include <math.h>
# define PI 3.14159 2653 5897 9
main()
{
int x=0 ;
double f=PI/180. 0 ;
while (x<=360) {
printf( "sin (%d) = %.16f\n",x ,sin (x*f ));
x++ ;
if(x % 23==0){
printf( "Dac a doriti sa continua ti,
apasati o tasta! \n");
getch() ;
}
}
}

3.5. Instruciunea do - while


Corespunde structurii repetitive condiionate posterior. Efectul ei
poate fi obinut cu celelalte instruciuni repetitive. Prezena ei n setul
de instruciuni asigur o mai mare flexibilitate i claritate programelor.
Sintaxa:

do
instructiune
while (expresie) ;
Efectul instruciunii do - while :
1. Se execut "instruciune";
2. Se evalueaz expresia;
3. Dac are valoarea "adevrat" (diferit de zero), se reia pasul 1.
4. Dac are valoarea "fals" (zero), execuia instruciunii do-while
se ncheie i se trece la instruciunea urmtoare din program.
Pe poziia "instruciune" este acceptat o singur instruciune C;
dac sunt necesare mai multe, acestea se includ ntre paranteze acolade
i formeaz astfel o singur instruciune, compus.
Observaii:
{
{
{
{

{ Blocul "instruciune" se execut cel puin o dat, chiar dac la


prima evaluare, expresie=0.
{ Dac n partea executabil apar instruciuni ca break , goto sau
return, se schimb aciunea normal a lui do-while, prin
ncheierea ei nainte de termen.
{ Pentru a nu confunda, n textul programului, un while din
do-while cu un ciclu while care conine instruciune vid, se
recomand folosirea acoladelor chiar i atunci cnd do-while
conine o singur instruciune.
do {
instructiune
}
while (expresie) ;
{ Execuia instruciunii do-while trebuie s produc modificarea
valorii "expresie" dup un numr finit de repetri; n caz contrar,
se produce un ciclu infinit.
Exemple:
# include <stdio.h>
# define PUNCT '.'
int main(void)
{
char car ;
int nr_sp=0 ;
printf("Introduceti o fraza terminata cu punct\n");
do {

car = getch();
if (car == ' ') nr_sp++;
}
while (car != PUNCT);
printf("S-au gasit %d spatii albe \n", nr_sp);
return 0 ;
}
Programul de mai sus contorizeaz numrul de spaii albe dintr-o
fraz, introdus de la tastatur. La citirea caracterului punct ".", ciclul
do-while se ncheie i se afieaz numrul de spaii.
Analog, se poate contoriza tastarea unui caracter oarecare, prin
modificarea condiiei din instruciunea if(car == ' ') nr_sp++;
exemplu:
if (car == 'd') nr_sp++ ;
Exemplul urmtor este un program care calculeaz rdcina
ptrat dintr-un numr real pozitiv, prin metoda iterativ, a lui
Newton. Se utilizeaz irul (x n ) ncN , definit prin recuren:
x 2n +a
x n+1 = 2$x
, x 0 = 1.
n
Acest ir este convergent i are limita L = a .
Termenii irului se calculeaz succesiv: x 0 , x 1 , , x N , pn cnd
diferena dintre doi termeni consecutivi este mai mic dact o valoare
impus, de exemplu,  = 10 10 :
x N x N1 [ 
Valorea lui x N este limita irului i deci x N = a .
#include <stdio.h>
#include <stdlib.h>
#define EPS 1e-10
//se calculeaza radical din a = numar real pozitiv
main( )
{
double a, x1, x2, y ;
printf("Introduceti valoarea lui a>0, a= ");
scanf(" %lf ", &a);
x2=1;
do {
x1=x2;
x2=0.5*(x1+a/x1);
if ((y=x1-x2)<0) y=-y ; // valoarea absoluta a lui y
}
while (y>EPS);
printf("a=% .11g \t radical(a)=% .11g\n", a, x2);

}
n program, x1 i x2 sunt dou valori consecutive ale termenilor
irului. Ele sunt necesare pentru estimarea erorii i oprirea procesului
de calcul, cnd eroarea este mai mic sau egal cu EPS.
Variabila y este utilizat pentru diferena a doi termeni
consecutivi, valoarea absolut a acesteia i compararea cu EPS.
Specificatorul .11g arat c valoarea se tiprete pe minim 11
spaii dup virgul, n formatul cel mai economic (normal sau cu
exponent).
Numrul de iteraii (de repetri ale buclei do-while) nu este
anterior cunoscut; el depinde de valorea EPS impus: cu ct aceast
valoare este mai mic, volumul de calcul (numrul de repetri) crete.
Pentru aflarea numrului de repetri, se poate introduce un contor,
iniializat cu zero, care este incrementat la fiecare trecere. n final,
valoarea contorului arat numrul de treceri prin do-while.

3.6. Instruciunea for


Corespunde structurii repetitive condiionate anterior, ca i n
cazul instruciunii while; spre deosebire de while, instruciunea for
utilizeaz trei expresii, fiecare avnd un rol propriu. Sintaxa:
for (expresie_1; expresie_2; expresie_3)
instructiune ;
Prin definiie, construcia de mai sus este absolut echivalent cu
secvena:
expresie_1;
while (expresie_2) {
instructiune ;
expresie_3 ;
}
Din construcia echivalent cu while, rezult c:
{ expresie_1 este interpretat ca o instruciune care se execut o
singur dat (iniializare);
{ expresie_2 este condiia logic de execuie a ciclului;
{ expresie_3 este interpretat ca instruciune de actualizare;

Instruciunea for este, n esen, un while dotat cu o iniializare i


o actualizare la fiecare iteraie, actualizare care se face dup execuia
instruciunii din corpul while.
Pe poziia "instruciune" este acceptat o singur instruciune C;
dac sunt necesare mai multe, acestea se includ ntre paranteze acolade
i formeaz astfel o singur instruciune, compus.
Observaii:
{ Att expresie_1 ct i expresie_3 pot lipsi:
for ( ;expresie _2; )
instructiu ne;
Ceea ce se obine este while, care este preferabil lui for din
punct de vedere al claritii. Dac lipsete i expresie_2, condiia este
tot timpul adevrat, ceea ce duce la repetare infinit:
for( ; ; ) instructi une;
determin un ciclu infinit, din care se poate iei numai cu break,
goto sau return .
{ Sub influena practicii din alte limbaje de programare, se
consider i se prezint uneori instruciunea for limitat la un ciclu
cu contor. Evident, cu for se pot implementa i cicluri cu contor
dar este greit s limitm posibilitile lui for la cicluri cu contor.
Fa de for din alte limbaje, n C instruciunea este mai complex.
Cele trei expresii
pot conine diferite instruciuni ale limbajului i se pot utiliza variabile
diferite:
#includ e <stdio.h>
int main(vo id)
{
float depunere= 1000 .0;
an=2000
for(pri ntf( "Dep ozit initial: \n") ;an+ +<=2 010;
printf( "Dep ozit in %d este %.2f\n",a n,de pune re))
depuner e=de pune re * 1.19;
return 0;
}
Programul de mai sus calculeaz suma dintr-un depozit bancar,
care beneficiaz de o dobnd anual de 19%, pentru o periad de 10
ani. Dobnda se vars n contul de depozit dup fiecare an iar dobnda
anului urmtor se calculeaz pentru suma majorat.
Prima expresie din for este un mesaj care se scrie o singur dat.
A doua expresie conine condiia de repetare i incrementarea
variabilei "an".

A treia expresie conine mesajul privind suma din depozit dup


fiecare an din perioada stabilit: 2000 - 2010.
{ O eroare frecvent este for (i=1; i=n; i++), care atribuie lui i
valoarea n. Dac n este diferit de zero, acest for va cicla la
infinit. O alt eroare este for (i=0; i==n; i++), n care se testeaz
dac i este identic cu n, uitnd c exp_2 este condiie de
continuare, nu de terminare.
Exemple:
1. Repetri cu numr cunoscut de pai:
double putere(do uble nr, int n)
{
double x_la_n;
int k ;
x_la_n= 1.0 ;
for (k=1 ; k <=n ; k++)
x_la_n *= x ;
return x_la_n ;
}
Funcia putere() primete ca argumente numerele x i n i
ntoarce valoarea x n , prin nmulire cu x, repetat de n ori.
2. Calcul de sume cu numr cunoscut de termeni:
float suma(int n)
{
float sum=0;
int k;
for (k=0 ; k <=n ; k++)
sum += 1.0/((k+1 .)*( k+2. ));
return sum ;
}
n

Funcia sum() calculeaz suma

 (k+1)(1 k+2)

, pentru valoarea lui n

k=0

specificat de argument.

3.7. Instruciunea continue


Se utilizeaz numai n corpul unei instruciuni repetitive (while,
do-while, for). Sintaxa:

continue;
Permite revenirea la nceputul blocului care se execut repetat,
nainte de ncheierea sa. La apariia instruciunii continue, se renun
la executarea instruciunilor urmtoare i se execut un salt la
nceputul blocului.
Prezena instruciunii mrete flexibilitatea programelor i
contribuie la rezolvarea mai simpl a unor ramificaii.
Nu se utilizeaz n cazul instruciunii switch.

3.8. Instruciunea break


Produce ieirea forat din instruciunile while, do-while, for,
switch. Dac sunt mai multe asemenea instruciuni incluse unele n
altele, se realizeaz numai ieirea din instruciunea curent (n care
este break ).
Sintaxa:
break;
n cazul instruciunii for, ieirea se realizeaz fr reactualizarea
expresiei exp_3.
n instruciunea switch, prezena lui break este strict necesar
pentru a produce ieirea, dup tratarea cazului selectat; n celelate
instruciuni, se poate evita break, prin includerea unor condiii
suplimentare n cmpul "expresie", care condiioneaz repetarea.

3.9. Instruciunea goto


Este o instruciune de salt necondiionat la o instruciune
etichetat, care se afl n aceeai funcie. Etichetele se definesc
printr-un nume, urmat de dou puncte ":".
Dei principiile programrii structurate recomand evitarea
folosirii lui goto, instruciunea se dovedete uneori util. De exemplu,
ieirea forat dintr-un ciclu inclus n altul (cu break se iese numai din
ciclul interior). n secvena urmtoare, se caut o valoare comun n
dou tablouri de ntregi, a i b:
for (i=0; i<n; i++)
for (j=0; j<m; j++)
if(a[i]==b[j]) goto ok;

pritf("Nu sunt elemente comune\n");


. . . . . . . . . . . . . . . . . . .
ok: printf("S-a gasit a[%d] = b[%d]=%d\n", i, j, a[i]);
. . . . . . . . . . . . . . . . . . .
O construcie cu goto se poate nlocui ntotdeauna cu una
structurat introducnd eventual variabile suplimentare. Secvena
anterioar se poate scrie:
int gasit = 0;
for (i=0; i<n && ! gasit; i++)
for (j=0; j<m && ! gasit; j++)
gasit = (a[i]==b[j]) ;
if (gasit)
printf("S-a gasit a[%d] = b[%d]=%d\n", i-1, j-1, a[i-1 ]);
else pritf("Nu sunt elemente comune\n");
. . . . . . . . . . . . . . . . . . .
Instruciunile break i continue permit construirea unor cicluri
while, do - while, for, cu mai multe puncte de ieire sau ramificaii
interne, evitnd folosirea instruciunii goto.
Din sintaxa instruciunilor while, do-while, for, rezult c
punctele lor de ieire pot fi doar la nceput (cnd blocul executabil nu
se parcurge nici mcar o dat) sau la sfrit. Sunt situaii, cnd ieirea
este necesar undeva "la mijloc", caz n care se folosete un ciclu
infinit i un break ntr-o instruciune if.

4 Funcii
4.1. Definirea i apelul funciilor
Funciile sunt elementele constructive ale limbajului C i sunt de
dou categorii:
{ funcii standard (de bibliotec) definite n fiiere *.h: pr in tf () ,
sc an f( ), s in () , st rlen () , ge tc ha r( ), p ut char () , . . . ;
acestea pot fi utilizate n programe, prin specificarea fiierului
header n care se afl.
{ funcii definite de utilizator, altele dect cele standard.
n programul surs, o funcie definit de utilizator, are dou
pri: antetul i corpul funciei. O funcie poate avea un numr de
parametri (variabile) dar o singur valoare - valoarea funciei. Antetul

conine informaii despre tipul valorii funciei, numrul i tipul


parametrilor i conine identificatorul (numele) funciei.
Structura unei funcii:
<tip > <nume > (< lista declaraiilor parametrilor formali >)
{
<declaraii >
<instruciuni >
}
Primul rnd este antetul; acesta apare obligatoriu naite de corpul
funciei dar i la nceputul programului (nainte de main()) situaie n
care se numete prototip .
n cazul tipurilor standard, <tip> din antet este un cuvnt cheie
care definete tipul valorii returnate de funcie.
n C exist dou categorii de funcii:
{ funcii cu tip, care returneaz o valoare, de tipul <tip>;
{ funcii fr tip, care nu returneaz nici o valoare dup execuie,
dar efectueaz anumite prelucrri de date; pentru acestea se va
folosi cuvntul rezervat void n calitate de <tip>.
O funcie poate avea zero, unul sau mai muli parametri. Lista
declaraiilor parametrilor (formali) este vid cnd funcia nu are
parametri.
n corpul funciei pot s apar una sau mai multe instruciuni
return, care au ca efect revenirea n programul apelant. Dac nu apare
nici o instruciune return, revenirea n programul apelant se face dup
execuia ultimei instruciuni din corpul funciei.
Valoarea returnat de o funcie. Instruciunea return
Forma general: return <expresie> ;
unde "expresie" trebuie s aib o valoare compatibil cu tipul declarat
al funciei. Efectul const n ncheierea execuiei funciei i revenirea
n programul apelant; se transmite valoarea calculat <expresie> ctre
programul apelant, reprezentnd valoarea f unciei, ce va nlocui
numele funciei. Dac funcia este de tip void, expresia lipsete.
Exemplu:
void fact( in t n); //calc ul ea za n!
{
int k; long fct;
if (n<0)
{

print f( "V aloa re a arg. negati va !"\n) ;


retur n; //nu se calcu le aza n!
}
for (fct=1 , k=1; k<=n; k++)
fct *=k;
print f(Va lo ar ea %d!=%l d\ n" , n, fct);
retur n; // s-a calcula t n! si s-a afisa t
}

{ Dac ntr-o funcie sunt mai multe comenzi return, prima ce se


execut va produce revenirea n programul apelant.
{ O funcie poate transmite prin return o singur valoare, de orice
tip (inclusiv structur) dar fr tipul tablou.
{ Valoarea returnat de o funcie cu tip trebuie s fie compatibil cu
tipul declarat al funciei; de exemplu, dac o funcie este definit
prin int f(), valoarea returnat poate fi de orice tip aritmetic,
deoarece aceasta poate fi convertit imediat la tipul ntreg:
int f()
{
int i, float a; char c;
. . . . . . . . . . . .
retur n i;
//valoa re a
. . . . . . . . . . . .
retur n a;
//valoa re a
. . . . . . . . . . . .
retur n c;
//valoa re a
. . . . . . . . . . . .
}

. . .
coincide cu tipul func ie i
. . .
"a" este conver ti ta la int
. . .
"c" este conver ti ta la int
. . .

{ Dac o funcie returneaz o valoare de tip adres (pointer),


cerinele sunt mai restrictive: tipul valorii returnate trebuie s fie
exact tipul funciei .
char *f()
{
char c, *adr_ c, tablo u_ c[ 5] , **adr_ ad r_ c;
int *adr_i ;
. . . . . . . . . . . .
c='A' ;
adr_c =&c;
retur n adr_c ; //corec t, adr_c = tipul funct iei
. . . . . . . . . . . .
*adr_ adr_ c= ad r_ c;
r et urn
* adr_ ad r_ c; // co rect ,* ad r_ ad r_ c=ti pu l
funct ie i
adr_c =tab lo u_ c;
retur n tablou _c ; //cor ect, numel e tabloul ui e un
//poi nter de acelas i tip
//cu tipul funct ie i
. . . . . . . . . . . .

retur n *adr_c ;/ /g resi t, se retur neaz a un carac te r


//in loc de adresa
. . . . . . . . . . . .
retur n adr_i; //gre si t, tipul pointe ru lu i (int) nu
//core sp un de cu tipul funct ie i
. . . . . . . . . . . .
retur n ;
//valo ar ea returna ta nu este defini ta

n cazul n care funcia are mai muli parametri, declaraiile de


tip pentru parametri se separ prin virgul.
Parametrii se utilizeaz pentru a permite transferul de date, ctre
o funcie, n momentul utilizrii ei n program. La construirea unei
funcii, se face abstracie de valorile concrete. Acestea vor fi prezente
numai la execuia programului.
Exemple:
void f(int a, int b, char c) /*corect, se specifica tipul fiecarui param.*/
/* incorect, nu se specifica tipul lui b */
void f(int a, b, char c)
Parametrii unei funcii, ca i variabilele definite n corpul funciei
sunt variabile locale, adic sunt valabile numai n interiorul funciei;
ele nu sunt recunoscute n exterior, nici ca nume, nici ca valoare.
n momentul compilrii, este necesar doar cunoaterea tipurilor
valorilor pe care le vor primi parametrii la execuie. Aceste declaraii
de tip sunt indicate n antetul funciei i vor servi compilatorului s
rezerve memoria necesar pentru fiecare parametru.
Parametrii declarai n antetul unei funcii se numesc formali,
pentru a evidenia faptul c ei nu reprezint valori concrete, ci numai
in locul acestora, pentru a putea exprima procesul de calcul. Ei se
concretizeaz la execuie prin valori ce rezult din apelarea funciei,
cnd sunt numii parametri efectivi.
Observaii:
{ Dac nu se specific tipul unei funcii, se consider automat c ea
va returna o valoare de tip int .
{ n limbajul C++, controlul cu privire la tipul valorii unei funcii
este mai strict i ca urmare se recomand, ca tipul s fie efectiv
specificat, n toate cazurile.
{ Pentru funcia principal main, se pot utiliza antetele:
int main()
int main(void)
void main()
void main(void)
main()
main(void)

Primele dou antete arat c funcia returneaz o valoare


ntreag; de asemenea ultimele dou antete arat c se returneaz o
valoare ntreag, chiar dac nu se specific tipul int n antet.
Se utilizeaz foarte frecvent varianta fr tip main().
Funcia main poate avea i parametri.
Programul urmtor produce tabelarea valorilor unei funcii de
dou variabile, n domeniul [0, 1]x[0, 1], cu pasul 0.1.
# include <stdi o. h>
doubl e fm(dou bl e, doubl e) ;
void main( vo id )
{
doubl e x,
y, pas=0.1 ;
for(x =0.0 ; x<=1.0 ; x=x+pa s)
for(y =0 .0 ; y<=1.0 ; y=y+pa s)
print f( "x =%lf y=%lf f(x,y) =% lf \n ", x, y,fm (x ,y )) ;
{
doubl e fm(dou bl e x, doubl e y)
{
retur n (3.0*x *y + 1.0)/(1 .0 + x + y + x*y);
}

Programul conine dou funcii: main() i fm(). Funcia


matematic fm() are doi parametri de tip real, doubl precizie i anume
x i y iar tipul valorii funciei este tot real, dubl precizie. Numele
funciei reprezint valoarea ntoars de aceasta, de exemplu, fm(0.0,
1.0)=0.5 este valoarea lui fm cnd argumentele sale iau valorile x=0 i
y=1.
Instruciunea return <expresie>, ntoarce valoarea expresiei n
programul apelant, aceast form fiind obligatorie la funciile cu tip.
Declaraia
double fm(double, double) ;
de la nceputul programului este un prototip al funciei fm(), care
descrie tipul funciei, numele i tipul fiecrui parametru. n acest mod,
funcia fm() este recunoscut n main(), chiar dac definiia ei se afl
dup main() .
n general, este indicat s se scrie prototipurile tuturor funciilor,
att ale celor definite n modulul curent de program, ct i ale celor
definite n alte module dar folosite n modulul curent.
Funciile de bibliotec sunt complet definite (inclusiv
prototipurile) n fiiere header, deci includerea acestor fiiere, asigur
i prezena prototipurilor. De exemplu, prototipul lui printf() este n
fiierul stdio.h .

Apelul funciilor se face difereniat, dup cum sunt cu tip sau fr


tip. O funcie fr tip se apeleaz prin:
nume( list de parametri actuali ); /* cand funcia are parametri
nume();
/* cand functia nu are parametri
O funcie cu tip se apeleaz n general prin:
variabil = nume( list de parametri actuali );
sau, mai general, prin specificarea numelui funciei ntr-o expresie n
care este permis tipul valorii funciei. Uneori nu ne intereseaz
valoarea pe care o returneaz o funcie i atunci funcia se apeleaz
exact ca o funcie fr tip. Un exemplu uzual este funcia printf(), care
ntoarce numrul de caractere scrise la consol, dar apelul uzual al
acestei funcii se face ca i cum ar fi de tip void .
Funciile au clasa de alocare implicit external, deci sunt vizibile
n toate modulele care compun aplicaia. Ca i la variabilele externe, o
funcie este vizibil din locul unde este declarat, pn la sfritul
fiierului surs. Din acest motiv se folosete un prototip al funciei
care se plaseaz la nceputul textului surs, n afara tuturor definiiilor
de funcii.
O alt posibilitate este includerea prototipurilor n
fiiere header. Fiierele header predefinite conin, ntre altele,
prototipuri ale funciilor de bibliotec. Prezena prototipurilor face
posibil verificarea la compilare a corespondenei dintre numrul i
tipul parametrilor formali cu numrul i tipul parametrilor actuali (la
apelare). Exemple de prototipuri:
1.
2.

float fmat (float x, float y, int n)


int fdet ( float, int, int, char)

Declaraiile parametrilor formali pot conine numele acestora (ca


n exemplul 1) sau pot fi anonime, deci fr a specifica un nume pentru
fiecare parametru (ca n exemplul 2).
n limbajul C, ordinea evalurii parametrilor actuali la apelul unei
funcii, nu este garantat. n fapt, aceast ordine este n general de la
dreapta la stnga, dar programele trebuie asfel construite nct s
funcioneze corect indiferent de aceast ordine. De exemplu, secvena:
int n=7;
printf (" %d %d\n", n++, n);
va tipri (la Borland C): 7 7 i nu 7 8 cum ar fi de ateptat.
Asemenea construcii trebuie evitate, prin separarea n mai multe
apeluri, de exemplu:
printf ("%d ", n++) ;

printf ("%d\n", n) ;
Acelai lucru este valabil i la expresii n care intervin apeluri de
funcii. De exemplu, n secvena urmtoare nu se garanteaz care
funcie va fi apelat prima:
y = f( ) - g( ) ;
Dac f() este apelat prima, i calculul valorii lui f() are efecte
asupra lui g() nu se obine acelai rezultat ca n cazul apelrii mai nti
a lui g(). Dac ordinea este important, aceasta se poate impune prin:
y = -g( ) ; y = y + f( );
Exemplul 1 - (varianta 1, corect)
Programul ilustreaz necesitatea folosirii prototipului. Se
calculeaz valoarea maxim dintr-un tablou de pn la 20 de numere
reale, introduse de utilizator.
#incl ude <stdio .h >
#incl ude <conio .h >
#defi ne MAX 20
d ou ble
ma x( do ub le
*t ab ,
i nt
n) ;/ /p roto ti pu l
funct ie i
void main()
{
doubl e x[MAX] ;
int i;
print f("\ n Tastat i numere le ,t er mina ti cu 0:\n") ;
for(i =0; i<MAX; i++)
{
scanf (" %l f",& x[ i] );
if(x[ i] == .0) break;
} // la iesir e din for, i=nr. el em en te intro du se
print f("\ n Maximu l este %lf ", max(x ,i )) ;
getch ();
}
doubl e max(do ub le *tab, in t n)
/* functia max (),de tip double ; parame tr ii sunt:
adres a tablo ul ui si numaru l de eleme nt e */
{
int i; doubl e vmax= *t ab ;
for(i =1;i <n ;i ++ )
if(vm ax <* (tab +i ))
vmax= *( ta b+ i);
retur n vmax;
}

Exemplul 1 - (varianta 2, incorect)


#incl ude <stdio .h >
#incl ude <conio .h >
#defi ne MAX 20

void main()
{
doubl e x[MAX] ;
int i;
print f("\ n Introd . numere le ,t er mina ti cu 0:\n") ;
for(i =0;i <M AX ;i ++ )
{
scanf (" %l f",& x[ i] );
if(x[ i] == .0) break;
} // la iesir e din for, i = nr.ele me nte
print f("\ n Maximu l este %lf ", max(x ,i )) ;
/ * f unct ia m ax e c onsi de ra ta d e ti p in t d eo ar ece nu
exist a proto ti p */
getch ();
}
doubl e max(do ub le *tab, in t n)
/* functia max (),de tip double ; parame tr ii sunt:
adres a tablo ul ui si numaru l de eleme nt e */
{
int i; doubl e vmax= *t ab ;
for(i =1;i <n ;i ++ )
if(vm ax <* (tab +i ))
vmax= *( ta b+ i);
retur n vmax;
}

La compilare, apare mesajul de eroare:


"Type mismatch in redeclaration of max", datorit nepotrivirii
tipului din definiie cu cel considerat implicit la apelul funciei;
soluie:
- definirea funciei naintea funciei main (incomod);
- folosirea prototipului.
Exemplul 1 - varianta 3 - corect---------#incl ude <stdio .h >
#incl ude <conio .h >
#defi ne MAX 20
d ou ble
ma x( do uble
ta b[ ], int
n) ;/ /p roto ti pu l
funct ie i
/ * t ab lo u u ni di me ns io nal
p entr u ca re nu treb ui e
pre ci za t n um ar ul de el em en te ;el treb ui e c un os cu t l a
prelu cr ar e si se trans fe ra ca parame tr u separa t */
void main()
{
doubl e x[MAX] ;
int i;
print f("\ n Introd . numere le ,t er mina ti cu 0:\n") ;
for(i =0;i <M AX ;i ++ )
{

scanf (" %l f",& x[ i] );


if(x[ i] == .0) break;
} // la iesir e din for, i = nr.ele me nte
print f("\ n Maximu l este %lf ", max(x ,i )) ;
getch ();
}
doubl e max(do ub le tab[] ,i nt n)
/* functia max (),de tip double ; parame tr ii sunt:
tablo ul tab[] si numaru l de element e */
{
int i; doubl e vmax= ta b[ 0] ;
for(i =1;i <n ;i ++ )
if(vm ax <t ab[i ])
vmax= ta b[ i] ;
retur n vmax;
}

Exemplul 2:
Programul surs este alctuit din dou fiiere, grupate ntr-un
proiect. Se calculeaz puterea ntreag a unui numr real.
// princ.c
#incl ude <stdio .h >
#incl ude <conio .h >
doubl e power( do ub le, int);
// protot ip funct ie
void main()
{
int i; double x;
print f( "\ n Numaru l real: ");
scanf (" %l f",& x) ;
print f( " Putere a intreaga : ");
scanf (" %d ",&i );
p ri nt f( "\n Re zu lt at ul e st e %l f\ n", p ow er (x ,i)) ;
getch () ;
}
// func.c
#incl ude <math. h>
doubl e power( do ub le a, int b)
{
int i, n; double p=1.; n=abs (b );
for(i =1 ; i<=n; i++)
p*=a;
if(b< 0) retur n (1/p);
else retur n( p) ;
}

Cele dou fiiere se pot compila separat, apoi se grupeaz ntr-un


proiect, folosindu-se meniul Project al mediului.
Se deschide un fiier proiect ( extensia .prj) care va conine
numele celor dou fiiere; dac la nume nu se adaug extensia se

consider c e vorba de fiierele sursa (cu extensia .c); se pot include


n proiect direct fiierele *.obj rezultate dup compilare.
Pentru a se crea modulul executabil, se selecteaz "Make EXE
file" din meniul Compile, i are loc compilarea (dac n proiect sunt
date fiiere surs), editarea de legturi i crearea fiierului EXE;
modulul EXE va avea acelai nume cu fiierul proiect; se va selecta
direct comanda Run pentru crearea fiierului EXE i execuie.
Exemplul 3 - (varianta 1, incorect)
Programul ilustreaz conversia automat de tip de date, la apelul
funciei dup regulile de conversie cunoscute.
Programul calculeaz cel mai apropiat numr ntreg, pentru un
numr real.
#incl ude <stdio .h >
#incl ude <conio .h >
void main()
{
float x; print f( "\ n Introd uc et i numaru l ");
scanf (" %f ",&x );
p ri nt f( "\n C el m ai a pr op ia t in tr eg es te % d" ,
n_int (x )) ;
getch () ;
}
/* deoarec e nu exista protot ip , functi a n_int este
consi dera ta de tip int iar param etru l i este
conve rtit autom at la double in defini ti a functiei ,
param etru l fiind tip float , apare eroare la
compi la re */
int n_int( fl oa t num)
{
if(nu m>0) {
if(nu m-(i nt )n um <0 .5 )
retur n((i nt )n um ); /* se return ea za o valoa re
intre aga prin conve rs ie
explic ita */
else retur n( (i nt )n um+1 );
}
else
{
if((i nt)n um -n um <0 .5 )
retur n( (i nt)n um );
else
retur n( (i nt)n um -1 );
}
}

Sunt mai multe puncte de ieire diferite, din funcia n_int dar
toate cu acelai tip de date de ieire.
Exemplul 3 - (varianta 2, corect)

#incl ude <stdio .h >


#incl ude <conio .h >
void main()
{
float x;
print f("\ n Introd ucet i numaru l ");
scanf ("%f ", &x );
p ri ntf( "\ n
Cel
ma i
ap ro pi at
in t
es te
n_int (x )) ;
// se face conver sia la doubl e a lui x
}
int n_int( do ub le num)
{
if(nu m>0)
{
if(nu m- (i nt)n um <0 .5 ) return (( in t) nu m);
else retur n( (i nt )n um +1 );
}
else
{
if((i nt )n um-n um <0 .5 ) return (( in t) nu m);
else retur n( (i nt )n um -1 );
}
}

% d" ,

Exemplul 3 - (varianta 3, corect)


#incl ude <stdio .h >
int n_int( fl oa t) ;
// nu e obliga to ri e prezen ta identif ic at or ul ui de
//par amet ru , este sufici en t tipul;
void main()
{
float x;
print f("\ n Introd ucet i numaru l ");
scanf ("%f ", &x );
p ri ntf( "\ n
Cel
ma i
ap ro pi at
in t
es te
% d" ,
n_int (x )) ;
}
int n_int( fl oa t num)
{
if(nu m>0)
{ if(num -( int) nu m< 0. 5) return (( in t) num) ;
else retur n( (i nt )n um+1 );
}
else {
if((in t) num- nu m< 0. 5) return (( in t) num) ;
else return (( in t) nu m-1) ;
}
}

TEM

1. S se scrie un program care s citeasca numere reale introduse


de la tastatur pna la citirea numarului 0 i s conin funcii care s
calculeze numrul minim, numrul maxim i media numerelor citite.
2. S se scrie un program care s numere cuvintele dintr-un text
de maxim 1000 de caractere de la tastatur. Cuvintele sunt separate
prin spaiu, caracterul Tab sau Enter. ncheierea citirii textului se face
prin apasarea tastei Esc ('\0x1b').
3. S se scrie un program care s calculeze i s afieze inversa
unei matrice de 3 linii i 3 coloane cu elemente ntregi citite de la
tastatur.

4.2. Transmiterea parametrilor la funcii


Transmiterea parametrilor actuali, ctre o funcie, se face:
{ prin valoare;
{ prin referin;
La transmiterea prin valoare, se copiaz valoarea fiecrui
parametru actual n spaiul rezervat pentru parametrul formal
corespunztor. Acest principiu de transfer face ca modificrile fcute
asupra parametrilor formali, n corpul funciei, s nu se reflecte n
afara ei, adic o funcie nu poate modif ica parametrii actuali cu
care este apelat, sau altfel spus, parametrii actuali sunt pentru
funcie nite constante predefinite.
Acest tip de transfer este
predefinit n C i nu poate fi modificat !
Transmiterea parametrilor actuali prin referin, se bazeaz pe
tipul de date pointer (adres). Parametrii actuali ai funciei sunt
declarai n mod explicit, pointeri. n acest tip de transfer, o funcie
primete indirect valorile actuale (prin adresele lor) i le poate
modifica tot n mod indirect, prin intermediul pointerilor de acces, de
care dispune.
Exemplu:
Pentru modificarea unor variabile, acestea nu se transfer direct
ci prin pointeri, o funcie neputnd modifica direct valorile variabilelor
din funcia care o apeleaz.

in

#incl ude <stdio .h >


#incl ude <conio .h >
void main()
{
v oi d s ch im ba(i nt * ,i nt *) ; / * pr ot ot ipul folo si t
main, local */
int x=2003 , y=0;
print f("\ n Valori initia le x=%d y=%d", x, y);

schim ba(& x, &y);


print f("\ n Valori finale
x=%d y=%d", x, y);
getch ();
}
void schim ba (i nt *a,in t *b)
{
int temp;
temp= *a ;
*a=*b ;
*b=te mp ;
}

Programul produce pe ecran:


Valori initiale : x=2003
y=0
Valori finale:
x=0
y=2003
n general, modul n care se transmit parametrii la funcii este
dependent de implementare. Unele principii generale, sunt ns valabile
la toate implementrile. Parametrii sunt transmii prin stiv. Ordinea
de plasare a parametrilor actuali n stiv este, n general, de la dreapta
la stnga, pentru lista de parametri din apelul funciei. Standardul nu
garanteaz ns acest lucru, i nici ordinea de evaluare a parametrilor.
Un program corect conceput, nu trebuie s depind de aceast ordine.
Descrcarea stivei se face, de regul, la majoritatea implementrilor de
ctre funcia apelant.
Regulile de mai sus sunt determinate de faptul c n C pot exista
funcii cu numr variabil de parametri (aceeai funcie poate fi apelat
o dat cu m parametri i alt dat cu n parametri etc.).
Un exemplu clasic este funcia printf(), care are n mod
obligatoriu numai primul parametru i anume adresa irului care
descrie formatul, restul parametrior fiind n numr variabil i pot chiar
lipsi. Modul de plasare asigur c, totdeauna, funcia printf() va gsi n
vrful stivei adresa irului de caractere care descrie formatul.
Analiznd specificatorii de format din acest ir, funcia printf() tie
ci parametri s-i extrag din stiv i ce dimensiune are fiecare
parametru ( 2 octei, 4 octei etc.).
De exemplu, la un apel de forma:
print f("% d %s\n", i, s);

n vrful stivei se va gsi adresa irului constant "%d %s\n" .


Funcia preia acest parametru din vrful stivei, citete irul i
extrage din stiv nc doi parametri: un ntreg i adresa unui ir de
caractere. Evident, dac parametrii actuali nu concord (ca numr i
tip) cu specificatorii de format, rezultatele vor fi eronate. Este clar

acum, de ce este necesar ca stiva s fie descrcat de ctre programul


apelant: acesta tie ci octei a ncrcat n stiv la apelarea unei
funcii. De exemplu, un apel de forma:
printf( "% d %d", n);

va produce o a doua tiprire, fr sens ( se va tipri, ca ntreg,


coninutul vrfului stivei nainte de apelul funciei), dar nu va duce la
situaii de blocare sau pierdere a controlului; revenirea n programul
apelant se face corect, iar acesta descarc stiva de asemenea corect.
Un apel de forma:
printf( "% d" , m, n);

va tipri numai variabila m, ignornd pe n iar restul aciunilor se


execut corect (revenire, descrcare stiv).

4.3. Biblioteci standard


Ofer o mare varietate de funcii, grupate dup tipul operaiilor
efectuate (familii de funcii). Pentru fiecare familie, exist un fiier
header (*.h), care conine prototipurile funciilor.
Principalele fiiere header sunt:
stdio.h

- conine funcii de intrare / ieire: printf(), scanf()

ctype.h

- conine funcii pentru testarea apartenenei la clase de


caractere: litere mari, litere mici, cifre, caractere tip
spaiu, de control etc.
- conine funcii pentru operaii cu iruri de caractere.

string.h
math.h

- conine funcii matematice: sin(), cos(), exp(), log(),


pow()

stdlib.h

- conine funcii utilitare: de conversie, de generare a


numerelor aleatoare, alocare eliberare memorie etc.

stdarg.h

- conine macroinstruciuni pentru crearea de funcii cu


numr variabil de argumente

time.h

- conine funcii pentru gestionarea timpului (data i ora):


numele zilei, lunii, anul, minutul, secunda, reprezentarea
datei;

limits.h
float.h

- n aceste fiiere sunt definite constante care dau valorile


minime i maxime pentru tipurile de baz; aceste valori
depind de implementare: Borland C, Turbo C, Turbo C++
etc.
- conine funcii standard pentru gestionarea ecranului n
modul text (25 linii i 40 sau 80 de coloane)

conio.h

graphics.h - conine funcii (peste 60) pentru gestionarea ecranului n


modul grafic (de ex. VGA: 480 x 640 pixeli); numrul de
pixeli depinde de tipul de monitor i de adaptorul grafic.
Pentru a utiliza o funcie, aceasta trebuie apelat. Forma general
a apelrii este:
nume_funcie(lista de parametri efectivi);
Cu excepia funciilor de tip void, orice funcie returneaz o
valoare care nlocuiete numele funciei.
Funciile de tip void se numesc procedurale, deoarece realizeaz
anumite prelucrri (operaii) dar nu returneaz o anumit valoare.

4.4. Intrri / ieiri standard: <stdio.h>


Conceptul de baz pentru intrri / ieiri standard este cel de
pointer la fiier. n orice implementare este definit (cu typedef) un tip
de structur, numit FILE.
n general, prin fiier nelegem o colecie ordonat de elemente
de acelai tip, numite nregistrri, organizate secvenial. Accesul la
date se poate face numai prin intermediul unei variabile care schimb
date cu o singur nregistrare, la un moment dat. Orice fiier are un
marcator "nceput de fiier" i unul "sfrit de fiier".
ICF

R1

R2

R3
xf

Rn-1

Rn

SFF

variabil de acces

Fig.1 Structura secvenial a unui fiier

n C exist o mare varietate de funcii pentru lucrul cu fiiere.


Acestea necesit informaii privind: numele fiierului, starea (deschis,
nchis), poziia curent n fiier (componenta accesibil) etc. Aceste
informaii sunt organizate ntr-o structur cu tip predefinit: FILE.
Orice operaie cu fiiere, se face prin intermediul unei variabile
de tip pointer la o structur de tip FILE. Pentru definirea acestei
variabile se utilizeaz declaraia:
FILE *adr_fisier;
4.4.1. Deschiderea unui fiier

Pentru deschiderea unui fiier se utilizeaz funcia fopen(). Ea


returneaz un pointer spre tipul FILE (ce indic adresa informaiilor
asociate fiierului) sau pointerul nul n caz de eroare. Prototipul este:
FILE *fopen (c onst char *nume_ fi si er , const char *mod);

unde:
{ nume_fisier este un ir de caractere - numele fiierului, care
depinde ca form de sistemul de operare; de exemplu, la MS-DOS
se scrie
"c:\\us er\\ list a.tx t"
este
un
ir de caractere care descrie modul de acces:
{ mod

"r"

deschide un fiier text pentru citire; fiierul trebuie s


existe

"w"

deschide un fiier text pentru scriere; dac fiierul exist,


coninutul lui se pierde; dac nu exist un fiier cu numele
specificat, el este creat.

"a"

deschide un fiier text pentru scriere prin adugare la


sfrit; dac fiierul nu exist, el este creat.
deschide un fiier text pentru actualizare, adic citire i
scriere

"r+"
"w+"

"a+"

deschide un fiier text pentru actualizare; dac fiierul


exist, coninutul lui se pierde; dac nu exist un fiier cu
numele specificat, el este creat.
deschide un fiier text pentru actualizare; fiierul poate fi
citit dar scrierea se face prin adugare la sfrit;
Exemplu:
FILE
*pf ;
pf=fope n("c :\\u ser\ \lis ta.t xt", "w");

Funcia fopen() creeaz un fiier pe discul c:\ cu numele lista.txt,


pregtit pentru scriere date i returneaz un pointer de acces la fiier.
Toate operaiile cu fiierul creat se realizeaz prin intermediul
variabilei de tip pointer pf, care este un identificator de fiier. Dac
operaia de deschidere fiier nu se poate realiza (disc protejat la
scriere, disc plin), funcia fopen() returneaz valoarea zero = NULL
care are semnificaia "nici o adres". De aceea, se recomand, ca la
deschiderea unui fiier s se testeze dac operaia a decurs normal. Se

poate combina deschiderea cu testul de realizare, ca n exemplul


urmtor:
FILE
*pf ;
if((p f=fo pe n( "c :\ \u se r\ \l is ta .t xt", "w ") )= =N UL L)
{
print f( "F isie ru l nu poate fi desch is ");
exit( 1) ;
}

Constanta NULL este predefinit n fiierul stddef.h i


corespunde unui pointer care nu indic o adres (pointerul nul).
Funcia exit() determin nchiderea tuturor fiierelor deschise,
ncheierea execuiei programului i transferul controlului ctre sistemul
de operare.
n tabelul de mai sus, modurile care conin + (actualizare), permit
scrierea i citirea din acelai fiier, mai precis prin acelai pointer.
Dac se pune sufixul b, se indic modul binar:
rb, wb, ab, r+b, w+b, a+b ;
Se poate pune explicit i sufixul t, indicnd explicit modul text:
rt, wt, at, r+t, w+t, a+t ;
Distincia dintre modul binar i text, depinde de sistemul de
operare (la MS-DOS exist diferene). Modul text este implicit.
Diferenele se refer la dou aspecte: tratarea caracterelor "\n" i
tratarea sfritului de fiier.
1. n modul text, gupul "\n" este convertit n dou caractere CR
(0xD) i LF (0xA). La citire, are loc conversia invers.
Dac se scrie n modul text i se citete n mod binar, nu se
obine aceeai informaie. Se recomand ca scrierea i citirea s se
realizeze cu acelai tip de funcii: fprintf / fscanf, fputs / fgets sau
fwrite / fread etc.
2. Tratarea sfritului de fiier. n ambele moduri (text i binar),
funciile de intrare/ieire gestioneaz numrul de octei din fiier. De
exemplu, funcia fgetc(), la terminarea citirii fiierului, ntoarce o
valoare ntreag predefinit EOF. Acest ntreg nu exist fizic n fiier
ci este generat de funcia fgetc(). n modul text ns, exist un caracter
special, 0x1A, ca ultim caracter al fiierului (exist fizic n fiier). La
ntlnirea acestui caracter, fgetc() va ntoarce EOF. Dac un fiier
conine numere ntregi sau reale, poate s apar un octet cu valoarea
26 (care se reprezint intern prin 0x1A). La citirea n modul text, nu se

poate trece de acest octet, el fiind interpretat ca sfrit fizic de fiier.


Citirea trebuie fcut n acest caz n modul binar.

4.4.2. nchiderea unui fiier


Toate fiierele deschise trebuie nchise nainte de terminarea
programului. Dac se omite nchiderea fiierelor pot s apar incidente
nedorite (distrugerea fiierelor, erori diverse). Funcia care nchide
ceea ce s-a deschis cu fopen() este fclose(), cu prototipul:
int fclose( FILE *pf);
unde pf este pointerul de identificare a fiierului. Dac valoarea
pe care o returneaz este zero, operaia a decurs normal iar dac nu
este zero, operaia de nchidere nu s-a putut realiza (fiierul este
inexistent sau este inaccesibil, cnd discul pe care se afla a fost scos
din unitate).

4.4.3. Citirea i scrierea unor caractere


{ Funcia putc() face scrierea unui caracter ntr-un fiier deschis cu
fopen():
int putc(in t caracter,
FILE *pf);
{ Funcia getc() face citirea unui caracter dintr-u fiier deschis cu
fopen():
int getc(FI LE *pf);
Pentru a citi tot fiierul se poate folosi secvena:
do
caracte r=ge tc(p f)
while (caracter !=EO F);
Funciile f open (),f clos e(), putc (),g etc( ) reprezint
un nucleu minim de funcii pentru lucrul cu fiiere.

5 Variabile globale i locale


Domeniu de valabilitate
La dezvoltarea unui program este util ca problema s fie
descompus n pri mai simple care s fie tratate separat (pe module).
Dezvoltarea modular a programelor este avantajoas i conduce la
programe structurate. Instrumentul prin care se poate aborda
programarea structurat este funcia. Funciile care compun programul
se pot afla n unul sau mai multe fiiere surs, care pot fi compilate
separat i ncrcate mpreun. Este strict necesar se transmit corect
informaiile ntre modulele de program.
{ Anumite informaii (valori de variabile) sunt utilizate numai n
interiorul funciei; ele se numesc variabile locale sau interne i
sunt definite i vizibile n interiorul funciei.
{ Alte informaii sunt necesare pentru mai multe funcii i se numesc
globale; ele se definesc n afara oricrei funcii iar funciile au
acces la acestea prin intermediul numelui variabilei. O variabil
global poate fi accesat ntr-un singur fiier (n care este definit)
sau n mai multe fiiere.
{ Variabilele globale, fiind accesibile mai multor funcii, pot
transmite informaii ntre funcii, fiind utilizate ca parametri
actuali ai funciilor.

5.1. Variabile locale


Sunt definite n interiorul unei funcii, pot fi utilizate i sunt
vizibile numai n interiorul funciei. Nici o alt funcie nu are acces la
variabilele locale, deoarece la ieirea din funcie, valorile lor sunt
distruse (se elibereaz memoria ocupat de acestea). Nu exist nici o
legtur ntre variabilele cu acelai nume din funcii diferite.
Limbajul C nu perm ite s se defineas o funcie n interiorul
altei funcii .
Cea mai mic unitate de program, n care se poate declara o
variabil local, este blocul = o instruciune compus, adic o secven
de instruciuni delimitat de acolade. Limbajul C permite ca ntr-o
instruciune compus s se introduc declaraii. O funcie poate
conine mai multe blocuri.
Durata de via a unei variabile este intervalul de timp n care
este pstrat n memorie.

{ Unele variabile pot rmne n memorie pe toat durata execuiei


programului, la aceeai adres; acestea sunt definite cu cuvntul
cheie STATIC - variabile statice sau permanente .
{ Alte variabile sunt definite i primesc spaiu de memorie numai
cnd se execut blocul sau modulul n care au fost definite. La
ncheierea execuiei blocului sau modulului, se elibereaz automat
memoria alocat i variabilele dispar. Dac se revine n blocul
respectiv, variabilele primesc din nou spaiu de memorie, eventual
la alte adrese. Variabilele de acest tip, sunt numite variabile cu
alocare automat a memoriei. Exemplu:
float functi e( )
{
int k ;
stati c int a[]={1 , 2, 3, 4};
. . . . . . . . . . . . . .
}

Variabilele
blocul n care au
Tabloul a[]
programului.
Variabila k

k i a[] sunt locale; domeniul lor de valabilitate este


fost definite.
pstreaz adresa de memorie pe toat durata execuiei
este cu alocare automat a memoriei.

5.1.1. Iniializarea variabilelor locale


La fiecare apel, variabilele cu alocare automat trebuie
iniializate. Dac nu se face iniializarea, acestea vor avea valori
ntmpltoare, care se afl n spaiul de memorie alocat. Variabilele
statice se iniializeaz o singur dat. Exemple:
void incre me nt ar e( void )
{
int i=1;
stati c int k=1;
i++ ; k++ ;
print f("i =% d \t k=%d \n", i, k);
}
int main(v oi d)
incre ment ar e( );
incre ment ar e( );
incre ment ar e( );
}

Rezultatul execuiei programului este:


i=2
i=2

k=2
k=3

i=2

k=4

Variabila i este cu alocare automat; ea este iniializat la fiecare


apel al funciei cu valoarea i=1.
Variabila k este de tip static, permanent; se iniializeaz o
singur dat iar valoarea curent se pstreaz la aceeai adres.
Variabilele statice dac nu sunt iniializate, primesc valoarea zero.
Variabilele statice interne (locale) ofer posibilitatea pstrrii
unor infoemaii despre funcie (de exemplu, de cte ori a fost apelat).

5.1.2. Variabile globale


Dup domeniul de valabilitate, variabilele pot fi ierarhizate
astfel:
{ variabile cu aciune n bloc (locale);
{ variabile cu aciune n funcie (locale);
{ variabile cu aciune n fiierul surs;
{ variabile cu aciune extins la ntregul program.
Pentru ca o variabil s fie valabil n tot fiierul, trebuie
declarat n afara oricrei funcii, precedat de cuvntul STATIC.
Dac fiierul conine mai multe funcii, variabila este vizibil n toate
funciile care urmeaz declaraiei.
Prin convenie, numele unei variabile globale se scrie cu liter
mare.
Sfera de aciune cea mai mare pentru o variabil este ntregul
program; o asemenea variabil este vizibil n toate funciile aflate att
din fiierul n care a fost definit, ct i din alte fiiere.
Pentru a crea o variabil global ea se declar n afara oricrei
funcii, fr cuvntul cheie STATIC.
float x ;
//var. global a in tot progr am ul
stati c float y ; //var . globa la in fisier ul curen t
int main(v oi d)
{
. . . . . . .
}

Variabilele globale mresc complexitatea unui program deoarece


ele pot fi modificate de orice funcie. Se recomand utilizarea lor
numai cnd este strict necesar.

6 Structuri de date: LISTE


6.1. Definiie
Multe aplicaii impun ca informaiile prelucrate cu calculatorul s
fie organizate sub forma unor liste de date.
O list ordonat sau liniar este o structur de date omogen, cu
acces secvenial format din elemente de acelai tip, ntre care exist o
relaie de ordine determinat de poziia relativ a elementelor; un
element din list conine o informaie propriu-zis i o informaie de
legatur cu privire la elementele adiacente (succesor, predecesor);
primul element din list nu are predecesor iar ultimul nu are succesor.
De exemplu, n lista candidailor nscrii la concursul de
admitere, un element conine urmtoarele informaii specifice:
- nume, prenume, iniiala tatlui;
- numarul legitimaiei de candidat;
- notele obinute la probele 1, 2;
- media la bacalaureat;
- media notelor;
Fiecare element al listei mai conine informaii cu privire la
poziia sa n list (predecesorul i succesorul).
Nr.
crt.

Numele i prenumele

Nr.
leg.

Nota 1 Nota 2 Media


bac.

Media
gen.

Popescu I. Ion Lucian

1/P

7.5

9.5

8.67

8.54

Anton Gh. Adrian Liviu

1/A

9.5

9.37

9.28

Moraru V. Vasile

5/M

8.5

9.5

9.89

9.22

Maraescu Gh. Liviu Costin

6/M

7.5

10

9.56

8.95

. . . . .

. . .

. . .

. . .

. . .

. . . .

Fiecare informaie poate reprezenta o ''cheie'' de acces pentru


cutare, comparaii, reordonare etc. De exemplu, lund drept cheie
media, se poate crea o nou list n care candidaii s apar n ordinea
descresctoare a mediei.
Coninutul unei liste se poate modifica prin urmtoarele operaii:
{ adugarea de noi elemente la sfritul listei;

{ inserarea de noi elemente n interiorul listei;


{ tergerea unui element din list;
{ schimbarea poziiei unui element ( modificarea unei nregistrri
greite);
{ iniializarea unei liste ca list vid, fr elemente, n vederea
completrii ei;
Alte operaii ce pot fi efectuate asupra listelor sunt cele de
caracterizare, care nu modific structura listelor, ci doar furnizeaz
informaii despre ele:
{ determinarea lungimii listei ( numrul de elemente);
{ localizarea unui element care ndeplinete o anumit condiie;
Operaii mai complexe:
{ separarea n dou sau mai multe liste secundare dup un anumit
criteriu;
{ combinarea a dou sau mai multe liste ntr-una singur;
{ crearea unei liste noi, prin selectareaelementelor unei liste pe
baza unui anumit criteriu.

6.2. Reprezentarea listelor n memorie


n memorie, listele pot fi reprezentate prin structuri statice
(tablouri) sau prin structuri dinamice ( liste nlnuite), folosind
pointeri.
n cazul reprezentrii statice, elementele sunt indexate prin
asocierea unui indice i se depun n locaii succesive de memorie.
Avantaje: timpul de acces este acelai la oricare element din
list, accesul se realizeaz prin intermediul indicilor iar implementarea
este simpl.
Dezavantaje: lungimea fix a listei (este obligatorie declararea
ei), introducerea i tergerea unui element n/din interiorul listei
implic deplasarea tuturor elementelor pe alte poziii dect cele
iniiale.

Zon de memorie
Parametrii de acces

sfrit

curent

Tablou de pointeri

P1 (adres)

Element 1
Element 2

P2
Element 3

P3

Element 4

P4

Pn-1

Element n-1

Pn
Element n
Figura 1

n cazul unei alocri statice (mai eficiente) a unei liste (fig.1), se


construiete un tablou de pointeri care permit accesul la informaia
util (elementele listei). Accesul la elemente se realizeaz prin doi
parametri: curent (indic poziia n tablou a adresei elementului
curent) i sfrit (indic poziia n tablou a adresei ultimului element).
n cazul reprezentrii dinamice, prin liste nlnuite, elementele
listei pot fi dispersate n toat memoria disponibil (se utilizeaz
eficient zonele libere) iar conectarea elementelor listei se realizeaz
prin pointeri care se adaug informaiei utile din fiecare element.
Avantajele reprezentrii dinamice sunt: lungimea variabil a listei
(pe parcursul programului de implementare, modificare), introducerea
i tergerea fr modificarea poziiei pentru celelalte elemente.
Principalul dezavantaj este durata de acces la un element care
depinde de poziia sa n list.
O structur dinamic de list conine patru parametri de acces
(fig. 2):
- lungimea listei (lungime), de tip ntreg, reprezint numrul de
elemente;
- adresa primului element (nceput ), de tip pointer;
- adresa elementului curent (curent), de tip pointer;
- adresa ultimului element (sfrit ), de tip ponter.
Pentru ca operaiile de inserare i tergere s se fac la fel i
pentru elementele de la capetele listei, se pot folosi nc dou elemente
false (santinele) plasate la nceput i la sfrsit. Astfel, toate elementele
utile din list au att predecesor ct i succesor.

Parametrii
listei

lungime

nceput

curent

...

sfrit

...

NIL
NIL

NIL
informaie
util

inf.

Element 1

inf.

...

inf.

...

inf.

...

...

Fig. 2

Fiecare element al listei conine informaia propriu-zis i doi


pointeri: unul reprezint adresa elementului urmtor - pointer de
legtur iar cellalt reprezint adresa de localizare a informaiei (nume
de persoan i date personale, linia de tabel pentru un candidat, datele
de identificare ale unei cri n bibliotec etc.)
Pentru introducerea (inserarea) unui element nou n list, se
modific valoarea pointerului de legtur din elementul predecesor i
se copiaz vechea valoare a acestui pointer (adresa elementului
urmtor) n pointerul de legtur al noului element .
Cutarea unui element se face prin parcurgerea secvenial a
listei, pn cnd pointerul curent ia valoarea dorit sau o component a
informaiei corespunde unei condiii.
Lista din fig.2 este o list simplu nlnuit - parcurgerea listei se
poate face ntr-un singur sens, dat de pointerii de legtur.
Pentru parcurgere n ambele sensuri, se introduc cte doi pointeri
de legtur n fiecare element (cte unul pentru fiecare sens de
parcurgere). Se obine astfel o list dublu nlnuit (fig.3), care
prezint avantaje suplimentare privind operaiile curente prin
micorarea timpului de acces la un element al listei cnd elementul
curent de acces este anterior sau posterior.
Prin legarea ntre ele a elementelor de la capetele unei liste dublu
nlnuite se confer listei o structur circular i se pot elimina
elementele "false" de marcare a capetelor (santinele).

Parametrii
listei

lungime

nceput

NIL

curent
...
...

sfrit
...
...

NIL
NIL

NIL
informaie
util

inf.

Element 1

inf.

...

inf.

...

inf.

...

...

Fig. 3

6.3. Implementarea operaiilor de baz pentru liste


simplu nlnuite
Presupunem c s-a definit un tip de date DATA, care este
specific informaiei dintr-un element al listei i depinde de aplicaie
(DATA poate fi un tip simplu sau tip structurat - tablou, structur etc.)
Definim structura C pentru un element al listei, astfel:
typed ef
struct
elem
{
DATA
data;
struc t
elem
*next;
}
ELEME NT ,
*LINK ;

Tipul ELEMENT corespunde unui element al listei iar tipul LINK


unui pointer la un element al listei. Lista este specificat printr-o
variabil de tip LINK care indic primul element al listei. O list vid
se va specifica printr-un pointer NULL. Cmpul next al ultimului
element al listei va conine NULL, indicnd c nu mai urmeaz nici un
element.
Se observ c o list simplu nlnuit este o structur ordonat,
care poate fi parcurs direct de la primul element ctre ultimul (ntr-un
singur sens). Parcurgerea n ambele sensuri nu se poate face direct, dar
se poate realiza prin folosirea unei structuri adiionale de tip stiv.

Totui, aceast variant nu este prea des folosit. Cnd sunt necesare
parcurgeri n ambele sensuri, se folosesc liste dublu nlnuite.
Pentru semnalarea situaiilor de eroare se va folosi urmtoarea
funcie, care tiprete irul primit i apoi foreaz oprirea programului:
void err_e xi t( co ns t char *s)
{
fprin tf (s tder r, "\n %s \n", s);
exit( 1) ;
}

O prim operaie care trebuie implementat este cea de creare a


unui element printr-o funcie n ew _el( ) . Spaiul pentru elementele
listei se creeaz la execuie, prin funcia standard m al loc( ) . Este
avantajos ca funcia de creare element s iniializeze cmpurile d at a i
nex t ale elementului creat. Aceast funcie va ntoarce un pointer la
elementul creat.
typed ef int DATA;
struc t el {DATA data; struct el *next; };
typed ef
struct el ELEME NT ,
*LINK;
LINK
{

new_el (D AT A x, LINK p)
LINK t = (LINK) malloc (siz eo f( EL EM EN T));
if (t==N ULL)
err_e xi t( "n ew_e l: Eroare de alocar e ");
t->da ta = x;
t->ne xt = p;
retur n t;

Se observ testarea corectitudinii alocrii cu mall oc () , ca i


conversia la tipul LINK i folosirea lui s iz eo f() pentru a obine
dimensiunea unui element. Se observ de asemenea c tipul DATA este
implementat ca un tip simplu sau ca o structur (ceea ce este
recomandat), atunci se poate face atribuirea t- >d at a = x , fr
probleme.
Iat un prim exemplu de creare a unei liste, pornind de la o
structur nrudit, anume de la cea de tablou. Funcia primete adresa
tabloului i numrul de elemente, ntorcnd un pointer la lista creat.
LINK arr_t o_ li st _i (DATA *tab, int n)
{
LINK t = NULL, p;
if (n>0) t=p=ne w_ el (* ta b+ +, NULL);
else
retur n NULL;
while (--n)

{
p->ne xt = new_e l( *t ab ++ , NULL);
p = p->nex t;
}
retur n t ;
}

Dac n nu este zero, se creaz un prim element i se memoreaz


n t i p , apoi se creaz succesiv elemente, incrementnd pointerul
ta b i avansnd pointerul p la urmtorul element. n final se ntoarce
t.
O a doua variant, mai clar, arr_ to _l is t_ r() , folosete
recursivitatea, testndu-se cazul de baz ( n == 0 ), n care se ntoarce
NUL L , altfel se ntoarce un pointer la un element creat cu new _e l( ) , cu
parametrii * ta b (elementul curent al tabloului) i un pointer ntors de
aceeai funcie ar r_ to _lis t_ r( ) , apelat cu parametrii ta b+ 1 i
Ar fi o greeal ca n loc de t ab +1 s se scrie ++ta b ,
--n .
deoarece codul ar depinde de ordinea de evaluare (care nu este
garantat !), iar tab apare n ambii parametri.
n particular, n Borland C, funcia ar fi incorect (sare peste
primul element).

LINK arr_t o_ li st _r (DAT A *tab, int n)


{
if (n==0) retur n NULL ;
e ls e
re tu rn
ne w_ el(* ta b,
arr _t o_ li st _r(t ab +1 ,
--n)) ;
}

6.4. Tiprirea unei liste


Se utilizeaz o funcie recursiv pentru tiprire, care nu este
dependent de tipul DATA (acest tip depinde de aplicaie). Funcia
tiprete cmpul data dintr-un element.
void print _d at a( DA TA x) {
print f( "% 6d", x) ;
}
void print _l is t( LI NK t)
{
if (t==N ULL) printf (" NULL \n");
else {
print _d at a(t- >d at a) ;

pritf (" -> ");


print _l is t(t- >n ex t) ; }
}

Pentru testarea funciei de tiprire se poate utiliza programul:


void main( vo id ) {
DATA a[]={0 0, 11, 22, 33, 44, 55, 66, 77, 88, 99};
print _l is t(ar r_ to _l is t_i( a, 10);
print _l is t(ar r_ to _l is t_r( a, 10);
}

Formele generale ale funciilor de parcurgere a listelor liniare n


variant iterativ i recursiv sunt:
void parc_ li st _r (L INK t)
{
if(t= =N UL L) {
print f( "L is ta este vid ! \n");
retur n;
}
else {
prelu cr ar e( t->d at a) ;
parc_ li st _r (t-> ne xt );
}
}
void parc_ li st _i (L INK t)
{
if(t= =N UL L) {
print f( "L is ta este vid ! \n");
retur n;
}
while (t != NULL ) {
prelu cr ar e( t->d at a) ;
t=t-> ne xt ;
}
}
S-a considerat o funcie pr el uc ra re() , care

realizeaz
operaiile dorite asupra datelor din fiecare element; funcia depinde de
tipul concret al datelor i deci este dependent de aplicaie.

6.5. Operaii asupra listelor liniare


Pentru o list liniar nu se poate implementa un algoritm de
cutare binar (prin divizarea listei i cutarea n seciuni) ci numai un
algoritm de cutare secvenial. Funcia de mai jos caut un element
dat ntr-o list, ntorcnd un pointer la primul element gsit, sau NULL
dac lista nu conine elementul dat. Se utilizeaz funcia cmp() de

comparare a dou elemente, ntorcnd un ntreg negativ, zero sau


pozitiv, la fel ca strcmp(). O asemenea funcie este strict necesar,
deoarece n C, structurile nu se pot compara.
int cmp(DA TA a, DATA b);
LINK list_ sr c_ i( LI NK t, DATA x)
{
while (t != NULL && cmp(t ->da ta , x)!=0)
t=t-> ne xt ;
retur n t;
}
LINK list_ sr c_ r( LI NK t, DATA x)
{
if(t= =N UL L || cmp(t -> data , x)==0)
retur n t;
else
retur n list_src _r (t -> ne xt ,x);
}

Ordinea n care apar condiiile conectate prin || sau && este


esenial: dac t este NULL, comparaia dintre t->data (care nu exist)
i x nu trebuie fcut.
Avantajul esenial al structurilor nlnuite fa de cele
secveniale (tablouri) apare la operaiile de inserare i tergere de
elemente.
La un tablou unidimensional, dac se terge un element, toate
elementele care urmeaz trebuie deplasate cu o poziie spre stnga,
pentru a pstra forma continu a tabloului (fr "goluri"). Similar, dac
se insereaz un element, toate elementele care urmeaz trebuie
deplasate spre dreapta cu o poziie, pentru a se realiza un spaiu pentru
noul element.
La structurile nlnuite, operaiile de inserare i tergere se
realizeaz fr schimbarea poziiei celorlalte elemente; se modific
doar cmpurile de adres care asigur "nlnuirea".

p
q
Fig.4 Inserare element

p
Fig.5 tergere element

6.5.1. Inserarea unui element dup un element dat


Se consider c pointerul p adreseaz elementul din list dup
care se va face inserarea iar q adreseaz noul element, care se
insereaz .
Dac p=NULL nu avem dup cine insera iar dac avem q=NULL,
nu avem ce insera. Dac p este adresa ultimului element din list
(adic p->next = NULL), se conecteaz noul element (q) la acesta i
atunci q devine ultimul element.
void ins_d up a( LI NK p, LINK q)
{
if(q= =N UL L || p==NU LL )
retur n;
q->ne xt =p ->ne xt ;
p->ne xt =q ;
}

Inserarea se face simplu, prin dou atribuiri de pointeri (fig. 4).


Trebuie observat un fapt esenial, c n instruciunile de atribuire
expresiile din stnga sunt toate obinute cu operatorul ->, deci se
modific efectiv lista. O atribuire de forma p= . . . ntr-o asemenea
funcie, unde p este un parametru formal, nu are nici un efect n
exteriorul funciei (transfer prin valoare).
6.5.2. tergerea unui element de dup un element dat
void del_d up a( LI NK p)
{
LINK q
if(p= =N UL L || p->ne xt ==NU LL )
retur n;
q=p-> ne xt ;
p->ne xt =q ->ne xt ;
free( q) ;

Dac p este NULL (nu exist element) sau p->next este NULL
(adic nu exist urmtorul element), nu avem nimic de fcut. Dac
elementul adresat cu p exist i nu este ultimul, se salveaz adresa de
legtur n p->next i apoi se elibereaz memoria ocupat de elementul
care se terge, adresat temporar cu q (fig.5).
6.5.3. tergerea tuturor elementelor unei liste
void del_l is t( LI NK t)
{
LINK p;
while (t != NULL ) {
p=t;
t=t-> ne xt ;
free( p) ;
}
}

Dup eliberarea spaiului indicat de un pointer, acel spaiu nu mai


poate fi folosit. Ca urmare, se salveaz t ntr-o variabil temporar p,
se atribuie lui t adresa urmtorului element i abia apoi se distruge
elementul curent. Dac s-ar scrie direct free(t), nu mai avem acces la
urmtorul element.

7 Structuri de date:

ARBORI

Organizarea liniar de tip list este adecvat pentru aplicaiile n


care datele (elementele din list) formeaz o mulime omogen i deci
se afl pe acelai nivel. n multe aplicaii, este strict necesar
organizarea ierarhic pentru studiul i rezolvarea problemelor.
Exemple:
- planificarea meciurilor ntr-un turneu sportiv de tip cup (fig.1);
- organizarea ierarhic a fiierelor n memoria calculatorului;
- structura de conducere a unei ntreprinderi, minister etc.;
- organizarea administrativ a unei ri.
Finala

SF1

sft.1

O1

SF2

sft2

O2

O3

sft3

O4

O5

sft4

O6

O7

O8

Figura 1.

n unele aplicaii, implementarea structurilor ierarhice n


programele de calculator este singura cale de rezolvare.
Un arbore este o structur ramificat format dintr-un nod A
(rdcina) i un numr finit de arbori (subarbori) ai lui A.
- orice nod din arbore este rdcin pentru un subarbore iar orice
arbore poate deveni subarbore;
- doi subarbori pot fi n relaie de incluziune, cnd un subarbore
este inclus n cellalt sau de excluziune , cnd nu au noduri comune.
Definiii:
nod = punct n care se ntlnesc doi subarbori;
nivel = numrul de noduri parcurse de la rdcin pn la un nod;
rdcin = nivel 1;
descendent = primul nod al unui subarbore;
nod terminal = nod fr descendeni;
nlimea unui nod = numrul maxim de niveluri;

arbore binar = arbore n care toate nodurile au 2 descendeni;


a

rdcina

d
h

e
i

g
m

subarbore drept

subarbore stng

Fig. 2 Arbore binar


Orice arbore binar are doi subarbori: stng i drept. Arborii
binari pot fi arbori de cutare i arbori de selecie; ei se caracterizeaz
prin faptul c fiecare nod areo"cheie" reprezentat printr-o informaie
specific de identificare a nodului. Cheia permite alegerea unuia din
cei doi subarbori n funcie de o decizie tip "mai mic", "mai mare", etc.
Un arbore este echilibrat dac pentru orice subarbore diferena
dintre nalimile subarborilor si este cel mult 1.

7.1. Parcurgerea arborilor


Prin parcurgerea arborelui nelegem inspectarea (vizitarea)
fiecrui nod i prelucrarea informaiei specifice lui. Pentru un arbore
dat, corespunztor unei anumite aplicaii, se impune o anumit ordine
de parcurgere.
n programe se utilizeaz algoritmi de parcurgere sistematic a
arborilor implementai sub form de proceduri. Procedurile eficiente de
parcurgere genereaz liste dinamice cu nodurile ce urmeaz a fi
examinate, care sunt reactualizate dup examinarea fiecrui nod; cnd
lista devine vid operaia de parcurgere se ncheie (au fost examinate
toate nodurile). Posibilitile de parcurgere sunt:
1. n lime , examinnd nodurile pe niveluri, n acelai sens.
2. n adncime: de la rdcin spre nodurile terminale sau
invers;
n cazul parcurgerii n lime algoritmul conine operaiile:

- se examineaz nodul rdcin i se formeaz lista (coada)


descen- denilor si ntr-o anumit ordine ( exemplu: de la stnga la
dreapta) ;
- se examineaz primul nod din coada de ateptare i se adaug la
coad descendenii si n ordinea stabilit;
- se repet operaia precedent pn cnd coada devine vid.

intrare

nod examinat

g
ieire

Fig. 3 Parcurgerea n lime

a
b
c
d
e
f
g
h
i
j
k

coada
b c d
c d
d e f
e f g
f g h i
g h i j
h i j k
i j k
j k
k
---------

n cazul parcurgerii n adncime , algoritmul conine operaiile:


- se examineaz nodul rdcin i se formeaz lista (stiva)
descen- denilor si ntr-o anumit ordine ( exemplu: de la stnga la
dreapta) ;
- se examineaz primul nod din stiva de ateptare i se introduc
n stiv descendenii nodului curent pn la cei terminali (ntregul
subarbore);
- se repet operaia precedent pn cnd stiva devine vid.

intrare

Rdcin

ieire

informaie
ataat

h
Subarbore stng

Subarbore drept

Fig. 4 Parcurgerea n adncime

Fig. 5

7.2. Implementarea arborilor binari

Un arbore binar are n general 3 componente: nodul rdcin,


subarbore stng, subarbore drept.
Cazul limit este un arbore vid, reprezentat printr-o constant
simbolic "ArboreVid". Tipul informaiei ataate nodurilor dintr-un
arbore este specificat n fiecare aplicaie.
De aceea vom considera c informaia din fiecare nod este
adresat indirect prin intermediul unui pointer (fig. 5). Cei doi
subarbori sunt de asemenea adresai indirect, fie prin pointeri (fig. 5),
fie prin intermediul indicilor de tablou n cazul implementrii statice.
Operaiile fundamentale specifice arborilor binari sunt:
a) Operaii care furnizeaz informaii despre un arbore:
{ testarea existenei unui arbore ( dac nu exist, este vid);
{ furnizarea adresei ctre un subarbore;
{ evaluarea numrului de noduri;
b) Operaii care modific structura unui arbore:
{ inserarea unui nod;
{ tergerea unui nod;

{ modificarea informaiei dintr-un nod;


{ tergerea arborelui (eliberarea spaiului de memorie).
Observaii:
1. Operaia de cutare nu este inclus n operaiile fundamentale
pentru c aceasta poate avea diferite variante, n funcie de tipul
arborelui prelucrat.
2. Arborii sunt utilizai (n mod frecvent) pentru a memora
informaii care se modific. De aceea, pentru reprezentarea lor se
prefer soluia dinamic, bazat pe utilizarea pointerilor. n
implementarea arborilor se utilizeaz o metod asemntoare cu cea de
la liste, prin construirea unui fiier cu operaiile fundamentale i
includerea acestuia n programele de aplicaii.
3. n afara operaiilor prezentate anterior, pot fi definite i altele,
necesare n diferite aplicaii.
Deoarece numrul de legturi ale unui nod este cunoscut (cel mult
2), se utilizeaz doi pointeri pentru nodurile din stnga i din dreapta,
adic rdcinile celor doi subarbori. Un subarbore vid, va fi
reprezentat prin pointerul NULL.
Presupunnd c s-a definit un tip de date DATA pentru cmpul de
informaie, urmtoarea structur C definete tipurile de date NODE i
BTREE (referin la NODE):
struc t node {
DATA d;
struc t node *left;
struc t node *right ;
typed ef struc t node NODE, *BTREE ;

O prim funcie este cea care construiete un nod.


BTREE new_no de (D AT A d)
{
BTREE t=(BTR EE ) malloc (s iz eo f( NO D));
if(t= =NUL L) err_e xit( "n ew _n od e: Eroar e de
aloca re ") ;
t->d= d;
t->le ft=t -> ri gh t= NU LL ;
retur n t;
}

Este util n aplicaii o funcie constructor de nod, care primete


att cmpul de date ct i cele dou cmpuri de legtur:
BTREE init_n od e( DA TA d, BTREE left, BTREE right)
{
BTREE t=new_no de (d );
t->le ft =l eft;
t->ri gh t= righ t;
retur n t;
}

Parcurgerea arborilor binari se face n trei moduri :


{ n preordine (RSD - rdcin, stnga, dreapta), adic se viziteaz
rdcina, subarborele stng i apoi subarborele drept; cei doi
subarbori vor fi tratai ca arbori n procesul de parcurgere,
aplicnd acelai algoritm - RSD.
{ n inordine (SRD - stnga, rdcin, dreapta), adic se parcurge
subarborele stng, se viziteaz rdcina i apoi subarborele drept;
cei doi subarbori vor fi tratai ca arbori n procesul de parcurgere,
aplicnd acelai algoritm - SRD.
{ n postordine (SDR - stnga, dreapta, rdcin), adic se
parcurge subarborele drept, cel stng i apoi se viziteaz rdcina;
cei doi subarbori vor fi tratai ca arbori n procesul de parcurgere,
aplicnd acelai algoritm - SDR.
Pentru arborele binar din fig. 2, aplicnd metodele
de mai sus, rezult urmtoarele iruri de noduri:
- preordine:
a b d h i e j k c f l m
- inordine:
h d i b j e k a l f m c
- postordine:
h i d j e k b l m f n o

de parcurgere
g n o ;
n g o ;
g c a ;

Definiiile celor trei metode de parcurgere sunt recursive, adic


se aplic la fel pentru orice subarbore; acest fapt sugereaz o
implementare recursiv a funciilor de parcurgere. Considerm o
funcie, visit() care examineaz informaia dintr-un nod, cu prototipul:
void visit (B TR EE );

Cele trei metode de parcurgere, se implementeaz n mod natural,


astfel:
void par_r sd (B TR EE t)
{
if(t! =N UL L) {
visit (t );

par_r sd (t -> left );


par_r sd (t -> righ t) ;
}
}
______ __ __ _____ __ __ __ _____ __ __ __ __ __ ____ _
void par_s rd (B TR EE t)
{
if(t! =N UL L) {
par_s rd (t -> left );
visit (t );
par_s rd (t -> righ t) ;
}
}
void par_s dr (B TR EE t)
{
if(t! =N UL L) {
par_s dr (t -> left );
par_s dr (t -> righ t) ;
visit (t );
}
}
_____ ____ __ __ __ __ __ __ __ __ __ __ __ ____ __ __ __ _

Pentru afiarea nodurilor parcurse (prin nume nod), sub form


indentat, se poate folosi o funcie pri nt _t re e( ) , recursiv, care
scrie un numr variabil de spaii, nainte de afiarea unui nod, n
funcie de nivelul nodului respectiv.
void print _t (B TR EE t, int n)// funct ie ajuta toar e
{
int i;
for(i =0 ; i<n; i++)
print f( " ");
if(t! =N UL L) {
print _n od e( t->d ); // funct ie de afisar e nod
print _t (t -> left , n+1);
print _t (t -> righ t, n+1);
}
else
print f( "A rb ore vid!" );
}
void print _t re e( BT REE t)
{
print _t (t , 0);
putch ar (' \n') ;
}

7.3. Arbori de cutare


Arborii de cutare sunt arbori binari n care exist o relaie de
ordine pe mulimea elementelor de tip DATA, acestea fiind cmpurile
de date din noduri. Categoria arborilor de cutare este caracterizat de
urmtoarele proprieti eseniale:
{ cmpurile de date din noduri conin valori distincte;
{ un arbore vid este, prin convenie, arbore de cutare;
{ rdcina unui arbore de cutare conine o valoare mai mare dect
toate valorile din subarborele stng i mai mic dect toate
valorile din subarborele drept;
{ subarborii stng i drept sunt arbori de cutare.
O important proprietate a arborilor de cutare, care decurge din
definiie, este aceea c parcurgerea n inordine produce o list de
elemente sortate cresctor, n raport cu cmpul DATA.
Aceast proprietate este utilizat de algoritmii de sortare intern.
A doua proprietate important a arborilor de cutare, care d
chiar denumirea lor, este posibilitatea implementrii unui algoritm
eficient de cutare a unui element, pe baza valorii din cmpul DATA:
se compar elementul cutat cu data din rdcin; apar trei posibiliti:
{ acestea coincid - elementul a fost gsit;
{ elementul cutat este mai mic dect data din rdcin - atunci
cutarea continu n subarborele stng;
{ elementul cutat este mai mare dect data din rdcin - atunci
cutarea continu n subarborele drept;
Presupunem c s-a definit o funcie de comparaie (care este
dependent de tipul DATA, deci de aplicaie):
int cmp_da ta (D AT A a, DATA b);

care returneaz o valoare negativ dac a< b , zero dac a=b sau o
valoare pozitiv, dac a>b .
Funcia de cutare, n variant recursiv, se poate scrie astfel:
BTREE cauta (BTREE t, DATA x)
{
int y;
if(t= =NUL L || (y=cm p( x, t-.d) )= =0)
retur n t;
t=(y< 0) ? t->le ft : t->rig ht ;
retur n cauta( t, x) ;
}
Funcia ca uta( ) primete un pointer t la rdcina arborelui i o
valoare x i returneaz pointerul la nodul gsit (care conine x ) sau
NULL dac valoarea x nu exist n arborele t .

Dac arborele de cutare este echilibrat (adic fiecare subarbore


are aproximativ acelai numr de noduri n stnga i dreapta), numrul
de comparaii necesare pentru a gsi o anumit valoare este de ordinul
lui log2 (n ) , unde n este numrul de noduri.
Dac arborele de cutare este neechilibrat, atunci cutarea este
asemntoare celei dintr-o list simplu nlnuit, caz n care numrul
de comparaii necesare, poate ajunge la n.
Petru a elimina restricia ca nodurile s aib elemente distincte,
se modific tipul DA TA , introducnd, pe lng valoarea propriu-zis, un
ntreg care reprezint numrul de repetri ale valorii respective.
Definim tipul informaiei din nod ca fiind KEY iar tipul DATA devine:
typed ef struc t {KEY key; int count;} DATA;
Definiia tipurilor N OD E i BT RE E nu se schimb dar funcia de
construcie a unui nod se modific, primind acum o valoare de tip KE Y

i iniializnd cu 1 cmpul count al nodului creat.


Inserarea unui element ntr-un arbore de cutare
Din definiia arborelui de cutare, rezult c adugarea unui nod
se face n funcie de valoarea cmpului de informaie; pentru adugarea
unui nod, este necesar aadar s se determine locul n care se
insereaz. Se fac deci dou operaii: cutarea locului i inserarea
propriu-zis.
Algoritmul de baz utilizat n construcia arborilor de cutare
este numit cutare i inserare .
Se d arborele t i un element x. Dac x este n t, se
incrementeaz cmpul count corespunztor valorii x; dac nu, x este
inserat n t ntr-un nod terminal, astfel nct arborele s rmn de
cutare .

Considerm c s-a definit o funcie de comparare, cm p_ ke y( )


care returneaz o valoare negativ, zero sau o valoare pozitiv, dup
cum relaia dintre argumente este a<b, a=b, a>b .
int cmp_ke y( KE Y a, KEY b);

O implementare recursiv a algoritmului de cuare i inserare


este:
BTREE cauta_ si _i ns r(BT RE E t, KEY x)
{
int c;
if(t= =N UL L) retur n new_no de (x );
if((c =cmp _k ey (x , (t->d ). ke y) )< 0)
t->le ft =c auta _s i_ in sr (t-> le ft , x);
else
if(c> 0)
t->ri gh t= ca uta_ si _i ns r( t- >rig ht , x);
else
(t->d).c ou nt ++ ;
retur n t;
}

Funcia primete pointerul t la rdcina arborelui i cheia x. Dac


s-a ajuns la un nod terminal sau arborele este vid, rezult c x nu este
n arbore i ca urmare se construiete un nod nou cu valoarea x.
Dac (t != NULL ) , se aplic algoritmul de cutare, apelnd
recursiv funcia cu subarborele stng, respectiv drept. Dac se
identific valoarea x n arbore, atunci se incrementeaz cmpul count
al nodului n care s-a gsit x. n final se returneaz pointerul la
arborele modificat.
7.3.1. Sortarea unui sir, bazat pe arbore de cutare
Se construiete un arbore de cutare cu elementele irului de
date, considerate ca elemente ale tabloului tab[]; apoi se parcurge n
inordine arborele creat returnnd elementele sale, n ordine, n acelai
tablou.
void sort( KE Y tab[] , int n)
{
BTREE t=NULL;
int i
for(i =0 ; i<n; i++)
t=cau ta_s i_ in sr (t , tab[i ]) ;
index =0;
parcu rge( t, tab);
delet e(t) ;
}

Funcia parcurge() preia elementele din arborele t i le plaseaz


n tabloul v[]:
stati c int index;
void parcu rg e( BT RE E t, KEY v[])
{
int j;
if(t! =N UL L) {
parcu rg e( t->l ef t, v);
for(j =0 ; j<(t-> d) .c ou nt; j++)
v[ind ex ++ ]= (t-> d) .k ey ;
parcu rg e( t->r ig ht , v);
}
}

Se parcurge t n inordine, copiind n v cheia din rdcin, de


attea ori ct este valoarea lui count. Este esenial declararea
indicelui de tablou, index, n clasa s tati c ex te rn al i iniializarea
lui cu 0, nainte de a apela funcia p ar cu rg e( ) n cadrul funciei
sor t( ) ; se asigur astfel incrementarea sa corect pe parcursul
apelurilor recursive ale funciei parcur ge () .
Funcia d el et e( ) (utilizat n so rt() ), elibereaz spaiul de
memorie alocat arborelui t, dup copierea datelor sortate, fiind i un
exemplu de parcurgere n postordine:
void delet e( BT RE E t)
{
if(t! =N UL L) {
delet e( t- >l eft) ;
delet e( t- >r ight );
free( t) ;
}
}

Apelul funciei free() trebuie s fie dup apelurile recursive,


deoarece, dup execuia lui free(t), variabila t nu mai exist.
Acest algoritm de sortare, combinat cu implementarea arborelui
prin alocare secvenial i anume chiar n tabloul care trebuie sortat,
duce la o metod eficient de sortare intern.

8 Tehnici de sortare
8.1. Introducere, definiii
Prin sortare se nelege ordonarea unei mulimi de obiecte de
acelai tip, pe baza unei componente de tip numeric, ce caracterizeaz
fiecare obiect.

De regul, obiectele sunt organizate sub form de fiier sau


tablou, n ambele cazuri ele formeaz un ir. Lungimea irului este dat
de numrul de obiecte (n).
Teoretic, obiectele sunt considerate
nregistrri (n C sunt
structuri):
R 0 , R 1 , . . . , R n1 ;
Fiecare nregistrare R k , are asociat o "cheie" C k , n funcie de
care se face sortarea. Pe mulimea cheilor se definete o relaie de
ordine, cu proprietile:
{ oricare ar fi dou chei C i , C j are loc numai una din relaiile:
C i < C j, C i = C j, C i > C j ;
{ relaia de ordine este tranzitiv.
Un ir este sortat dac oricare ar fi i, j c 0, 1, . . . , n 1 , i < j ,
avem C i [ C j . Aceast definiie implic o sortare n sens cresctor pe
mulimea cheilor. n cele ce urmeaz, algoritmii de sortare vor fi
implementai pentru sortare cresctoare, fapt ce nu micoreaz gradul
de generalitate; dac dorim ca irul final s fie sortat descresctor, el
va fi citit i nregistrat de la componenta n-1 ctre 0.
Un algoritm de sortare se numete stabil, dac nregistrrile cu
chei egale rmn, n irul sortat n aceeai ordine relativ n care se
gseau n irul iniial ( nu se schimb ordinea nregistrrilor egale).
Metodele de sortare se pot clasifica n dou mari categorii:
{ metode de sortare intern, n care nregistrrile se afl n
memoria intern i sunt rapid accesibile;
{ metode de sortare extern, n care nregistrrile se afl pe suport
extern (disc) iar timpul de acces (mare) face inacceptabil accesul
repetat la nregistrri.
Dac nregistrrile ocup spaiu mare n memorie (multe locaii
de memorie / nregistr.), se prefer construirea unui tablou de adrese
ale nregistrrilor. n loc de muta nregistrri, se mut adrese n tabel,
cu o important economie de timp. Dac privim adresele nregistrrilor
ca indici, tabloul de adrese conine n final permutarea indicilor, care
permite accesul la nregistrri n ordinea cresctoare a cheilor.
Aceast metod se numete sortare prin tablou de adrese .
n cazul sortrii interne, dac nregistrrile nu ocup volum mare
de memorie, ele vor fi mutate efectiv iar metoda se numete sortare de
tablouri .

8.2. Funcii polimorfice

Aceste funcii sunt construite pentru a se putea aplica oricror


tipuri de date. Evident c prelucrrile concrete vor fi diferite de la un
tip de date la altul dar aceste aspecte concrete se pot transfera
funciilor specifice de prelucrare. Transmitnd funciei polimorfice un
pointer la funcia de prelucrare i utiliznd peste tot tipul de pointer
universal void *, se poate realiza uor o funcie polimorfic.
De
exemplu,
funcia
standard,
de
bibliotec
qsort()
implementeaz algoritmul de sortare intern Quicksort, avnd
prototipul:
void qsort (v oi d *tab, size_t n, size_t dim,
int (*cmp) (c on st void *, const void *));

Se sorteaz tabloul tab, cu dimensiunea n, fiecare element din


tablou avnd dimensiunea dim iar cmp este un pointer la o funcie care
primete doi pointeri la dou elemente ale tabloului, returnnd
valoare<0, 0 sau valoare>0 dup cum este mai mare al doilea, sunt
egale sau este mai mare primul element.
Exemple:
1. Pentru sortarea unui tablou de ntregi, funcia de comparaie
trebuie s fie:
int cmp_in t( co ns t void *a, const void *b)
{
retur n *((int *) a - *((int *) b;
}

Se convertesc pointerii (void *) la (int*), se ia apoi coninutul


fiecruia (deci doi ntregi), se face diferena lor, care se returneaz ca
rezultat. Apelul va fi n acest caz:
int tab_in t[ 10 0] ;
qsort (tab _i nt , 100, sizeof (i nt ), cmp_i nt );

2. Pentru sortarea unui tablou de 100 de iruri de caractere,


fiecare cu lungimea maxim de 50 de octei, declarat prin:
char a[100] [5 0] ;

n condiiile n care se dorete s se modifica efectiv poziia din


memorie a celor 100 de iruri, funcia de comparaie va fi:
int cmp_si r( co ns t void *a, const void *b)
{
retur n strcmp (a , b);
}

Se utilizaez funcia standard de comparaie a irurilor strcmp();


apelul funciei de sortare va fi:
qsort (a, 100, 50, cmp_s ir );

ceea ce pune n eviden c un element al tabloului este de 50 de


octei.
3. Definim o structur i un tablou de structuri:
typed ef struc t {int key; char *sir} STRUCT _1 ;
STRUC T_1 tab[10 0] ;

Funcia de comparaie pentru cutarea unui element n tablou,


dup cheia numeric key, se definete acum astfel:
int cmp_st r_ 1( co ns t void *key, const void *elem)
{
retur n *((int *) key) - ((STR UC T_ 1 *) elem) ->
key;
}

Dac valoarea returnat este zero, elementul cutat a fost gsit.


Aici key este adresa unui ntreg (cheia numeric) iar elem este adresa
unui element al tabloului, deci adresa unei structuri de tipul
STRUCT_1, deci este vorba de tipuri diferite. Se convertete pointerul
key la (int *) i apoi se ia coninutul, apoi se convertete pointerul
elem la (STRUCT_1 *) i se ia cmpul key al structurii indicate de
elem. Se ntoarece diferena celor doi ntregi.
Dac dorim acum s sortm tabloul de structuri de mai sus, cu
funcia polimorfic qsort(), dup cheia key, trebuie modificat funcia
de comparaie, deoarece se primesc de data asta adresele a dou
elemente de acelai tip (elementele tabloului).
int cmp_st r_ 2( cons t void *a, const void *b)
{
retur n ((STRU CT _1 *)a)- >k ey - ((STR UC T_ 1 *)b) - >key;
}

Pentru generalitate, toi algoritmii de sortare vor fi implementai


prin funcii polimorfice, nct s se poat sorta orice tip de tablou,
fr modificri n program. Pentru aceasta, vom considera o funcie
extern care compar dou nregistrri, pe baza adreselor lor din
memorie (de tip void * pentru generalitate) i care ntoarce o valoare
ntreag negativ, zero sau o valoare pozitiv, n funcie de relaia
dintre cele dou nregistrri (<, =, >).

Definim, pentru aceasta, tipul de date:


typed ef int (*PFC MP) (const void *, const void *) ;

adic un pointer la funcia de comparaie. Toi algoritmii vor fi


implementai dup prototipul:
void sort( vo id *v, size_ t n, size_ t size, PFCMP cmp);

n care v este adresa de nceput a tabloului de nregistrri, n este


numrul de nregistrri, size este dimensiunea n octei a unei
nregistrri iar cmp este pointerul la funcia care compar dou
nregistrri.
Dup apelul funciei de sortare, tabloul v va conine nregistrrile
n ordinea cresctoare a cheilor.
Procednd n acest mod, partea specific a problemei (structura
nregistrrii, poziia i natura cheii, tipul relaiei de ordine) este
preluat de funcia de comparaie i detaat de algoritmul propriu-zis,
ceea ce confer algoritmului un mare grad de generalitate.
Pentru transferul nregistrrilor dintr-o zon de memorie n alta,
considerm dou funcii externe, swa p( ) - care schimb ntre ele
poziiile din memorie a dou nregistrri i c op y( ) - care copiaz o
nregistrare dat la o adres de salvare.
void swap( vo id *v, size_ t size, int i, int j);
void copy( vo id *a, void *b, size_t size) ;
Prima funcie schimb ntre ele nregistrrile v[ i] i v[ j] iar a

doua copiaz nregistrarea de la adresa b (surs) la adresa a


(destinaie).
Dimensiunea fiecrei nregistrri este de si ze octei. Calculul
adresei se face prin relaia ( BY TE * )v + i *s iz e , unde tipul B YT E
este definit de:
typed ef unsig ne d char BYTE;

Metodele de sortare intern se clasific n trei categorii:


{ sortare prin interschimbare;
{ sortare prin inserie;
{ sortare prin selecie.
Fiecare din aceste categorii cuprinde att metode elementare, n
general neeficiente pentru blocuri mari de date, ct i metode evoluate,
de mare eficien. Metodele elementare au avantajul c sunt mai uor
de neles i au implementare simpl. Ele se pot folosi cu succes pentru
tablouri de dimensiuni relativ reduse (sute, mii de nregistrri).

8.3. Eficiena algoritmilor de sortare


Este natural problema comparrii algoritmilor. Ce este un
algoritm eficient i de ce unul este superior altuia ? Analiza eficienei
unui algoritm se face, de regul n trei situaii: ir iniial sortat, ir
aleator i pentru ir iniial sortat n ordine invers.
Pentru algoritmii care nu necesit spaiu suplimentar de memorie,
comparaia se face dup numrul de operaii efectuate, care n esen
sunt: comparaii de chei i transferuri de nregistrri. Transferurile
se pot detalia n interschimbri i copieri (o interschimbare necesit 3
copieri).
Evident, numrul de operaii depinde de dimensiunea n a
tabloului care este supus sortrii. Natura acestei dependene este cea
care difereniaz algoritmii de sortare. Se definete o mrime care
depinde de n, numit ordinul algoritmului O(n) .
Dac numrul de operaii se exprim printr-o funcie polinomial
de grad k, se spune c ordinul este O(n k ). De exemplu, dac numrul de
operaii este n(n-1)/2, ordinul este O(n 2 ) .
Algoritmii care apar n domeniul prelucrrii datelor sunt de ordin
polinomial, logaritmic sau combinaii ale acestora.
n algoritmii de sortare, apar ordinele n 2 i n*log(n).
Un algoritm bun de sortare este O(n*log(n)); algoritmii
elementari sunt de ordin O(n 2 ).

8.4. Sortare prin interschimbare: Bubblesort


Cea mai simpl este interschimbarea direct sau metoda bulelor.
Pentru v[i] fixat, se compar v[j] cu v[j-1] pentru j=n-1, n-2, . . .
, i. i se face un schimb reciproc de locuri n tablou, atunci cnd nu se
respect condiia de sortare cresctoare v[j]>=v[j-1]. La sfritul
primei etape, pe locul v[0] ajunge, evident cel mai mic element.
Dup a dou trecere, pe locul v[1] va ajunge , cel mai mic
element din cele rmase, s.a.m.d.
Se procedeaz analog pn la ultimul element, care nu se mai
compar cu nimic, fiind cel mai mare.
Numrul de interschimbri este n(n-1)/2, n cazul cel mai
defavorabil, cnd irul iniial este sortat invers (dar noi nu tim !).

O cretere a eficienei se poate realiza astfel: dac la o trecere


prin bucla interioar (de comparri), nu s-a fcut nici o inversare de
elemente, nseamn c irul este sortat i algoritmul trebuie oprit.
Acest lucru se realizeaz prin introducerea unei variabile logice
sor ta t care este iniializat cu 1 i pus n 0 la orice interschimbare.
Se reduce numrul de operaii n cazul unui tablou iniial sortat (la n-1)
i cresc performanele n cazul unui ir aleator.
Denumirea metodei provine din faptul c elementele v[i], de
valoare mare, se deplaseaz ctre poziia lor final (urc n ir), pas cu
pas, asemntor bulelor de gaz ntr-un lichid.
void bubbl e_ so rt (v oid *v, size_t size, PFCMP cmp)
{
int i, j, sortat ;
BYTE *a=(B YT E *) v;
sorta t= 0;
for(i =1 ; i<n && !sorta t; i++) {
sorta t= 1;
for(j =n -1 ; j>=i; j--)
if(cmp( a+ (j -1 )* size , a+j*si ze )>0)
{ swap( a, size, j-1, j);
sortat =0 ;
}
}
}

8.5. Sortarea prin partiionare i interschimbare:


Quicksort
Se poate ncadra ntr-o tehnic mai general, "Divide et impera"
(mparte i stpnete), deoarece se bazeaz pe divizarea irului iniial
n iruri din ce n ce mai scurte.
Algoritmul Quicksort, fundamemtat n 1962 de C. A. R. Hoare,
este un exemplu de algoritm performant de sortare, fiind de ordinul
n*log(n).
Este un algoritm recursiv, uor de implementat n C.
Funcia care realizaez sortarea primete ca date de intrare o
parte a tabloului ce trebuie sortat, prin adresa de nceput i doi indici
left i right . Iniial funcia se apeleaz cu indicii 0 i n-1.
Se alege un element arbitrar al tabloului v, numit pivot, care se
noteaz cu mark, uzual m ar k = v [( le ft +r ig ht )/ 2] . Se divide
tabloul n dou pri n raport cu mark: toate elementele mai mici
dect mark trec n stnga iar cele mai mari dect mark trec n dreapta.

n acest moment elementul mark se afl pe poziia final iar tabloul


este partiionat n dou tablouri de dimensiuni mai mici.
Dac notm cu k indicele pivotului, putem apela aceeai funcie
de sortare cu limitele le ft i k- 1 pentru partea stng i cu limitele
k+1 i right pentru partea dreapt.
Cnd se realizeaz condiia left>=right, algoritmul se ncheie.
Exist mai multe variante de implementare; n exemplul de mai
jos, se utilizeaz doi indici, i i j, care sunt iniializai cu left,
respectiv cu right. Ct timp v[i]<mark, incrementm i, apoi ct timp
v[j]>mark, decrementm j. Acum, dac i<=j, se face interschimbarea
v[i] cu v[j], actualiznd similar indicii i i j. Procesul continu pn
cnd i>j.
S-a obinut urmtoarea partiionare:
- toate elementele v[ le ft], v[ le ft +1 ], ..., v[ i- 1] sunt mai
mici ca mark;
- toate elementele v [j +1 ],v[ j+ 2] ,. .. ,v[r ig ht ] sunt mai mari
ca mark;
- toate elementele v[i],. .. ,v [j ] sunt egale cu mark;
Acum se apeleaz aceeai funcie cu indicii le ft,j , respectiv i ,
right (dac left<j i i<right).
Pentru eficien, variabilele intens folosite n etapa de
partiionare (i i j) sunt declarate n clasa register.
void quick _s or t(BY TE *v ,s iz e_ t size,i nt left,
int right ,PFC MP cm p)
{
regis ter int i, j;
BYTE *mark ;
i=lef t; j=rig ht ;
switc h(j- i) {
case 0: retur n;
case 1: if(cm p( v+ i* si ze, v+j*si ze )> 0)
swap(v, size, i, j);
return ;
defau lt : break;
}
mark= (BYT E* )m al lo c( si ze );
copy( mark , v+((le ft+r ig ht )/ 2) *s ize, size) ;
do {
while (c mp (v+i *s iz e, mark) <0 )
i++;
while (c mp (v+j *s iz e, mark) >0 )
j--;
if(i< =j ) swap(v , size, i++, j--);
} while (i<=j);
if(le ft<j )
quick_s or t( v, size, left, j, cmp);
if(i< righ t)
quick_s or t( v, size, i, right, cmp);
free( mark );
}

void Quick (v oi d *v, size_t n, size_t size, PFCMP


cmp)
{
quick _s or t(v, size, 0, (int) n-1, cmp);
}

Analiza algoritmului Quicksort


aleatoare, el este de ordin O(nlog(n)).

arat

c,

cazul

datelor

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