Sunteți pe pagina 1din 82

Manual de Informatic pentru licen

valabil ncepnd cu sesiunea iulie 2014


Specializarea Informatic
Tematica general:
Partea 1. Algoritmic i programare
1. Cutari (secvenial i binar), sortri (selecie, bubblesort, quicksort).
2. Metodele backtracking i divide et impera.
3. Concepte OOP n limbaje de programare (Python, C++, Java, C#): Clase i obiecte.
Membrii unei clase i specificatorii de acces. Constructori i destructori.
4. Relaii ntre clase. Clase derivate i relaia de motenire. Suprascrierea metodelor.
Polimorfism. Legare dinamica. Clase abstracte i interfee.
5. Liste, Dicionare. Specificarea operaiilor caracteristice (fr implementri)
6. Identificarea structurilor i tipurilor de date potrivite pentru rezolvarea problemelor
(doar dintre cele de la punctul 5.). Folosirea unor biblioteci existente pentru aceste
structuri (Python, Java, C++, C#).

Partea 2. Baze de date


1. Baze de date relaionale. Primele trei forme normale ale unei relaii.
2. Interogarea bazelor de date cu operatori din algebra relaional.
3. Interogarea bazelor de date relaionale cu SQL (Select).

Partea 3. Sisteme de operare


1. Structura i funciile sistemelor de operare. Sisteme de fiiere.
2. Procese Unix: creare, funciile fork, exec, exit, wait; comunicare prin pipe i FIFO.
3. Programare shell Unix i comenzi Unix de baz: cat, cp, cut, echo, expr, file,
find, grep, less, ls, mkdir, mv, ps, pwd, read, rm, sort, test, wc, who.

Cuprins
1.

ALGORITMIC I PROGRAMARE ....................................................................... 3


1.1. CUTRI I SORTRI ............................................................................................... 3
1.1.1.
Cutri .......................................................................................................... 3
1.1.2.
Sortri ........................................................................................................... 5
1.2. METODELE BACKTRACKING I DIVIDE ET IMPERA..................................................... 8
1.2.1.
Metoda backtracking...................................................................................... 8
1.2.2.
Metoda "divide et impera" ........................................................................... 12
1.3. CONCEPTE OOP N LIMBAJE DE PROGRAMARE ....................................................... 13
1.3.1.
Noiunea de clas......................................................................................... 13
1.4. RELAII NTRE CLASE............................................................................................ 22
1.4.1.
Bazele teoretice............................................................................................ 22
1.4.2.
Declararea claselor derivate........................................................................ 22
1.4.3.
Funcii virtuale ............................................................................................ 24
1.4.4.
Clase abstracte ............................................................................................ 28
1.4.5.
Interfee ....................................................................................................... 30
1.5. LISTE I DICIONARE ............................................................................................ 32
1.5.1.
Liste............................................................................................................. 32
1.5.2.
Dicionare.................................................................................................... 36
1.6. PROBLEME PROPUSE ............................................................................................. 38

2.

BAZE DE DATE ....................................................................................................... 40


2.1. BAZE DE DATE RELAIONALE. PRIMELE TREI FORME NORMALE ALE UNEI RELAII ... 40
2.1.1.
Modelul relaional ....................................................................................... 40
2.1.2.
Primele trei forme normale ale unei relaii................................................... 43
2.2. INTEROGAREA BD CU OPERATORI DIN ALGEBRA RELAIONAL .............................. 50
2.3. INTEROGAREA BAZELOR DE DATE RELAIONALE CU SQL....................................... 53
2.4. PROBLEME PROPUSE ............................................................................................. 59

3. STRUCTURA I FUNCIILE SISTEMELOR DE OPERARE. SISTEME DE


FIIERE ............................................................................................................................ 60
3.1. SISTEMUL DE FIIERE UNIX................................................................................... 60
3.1.1.
Structura Intern a Discului UNIX............................................................... 60
3.1.2.
Tipuri de fiiere i sisteme de fiiere............................................................. 63
3.2. PROCESE UNIX ..................................................................................................... 67
3.2.1.
Principalele apeluri system de gestiune a proceselor ................................... 67
3.2.2.
Comunicarea ntre procese prin pipe ........................................................... 71
3.2.3.
Comunicarea ntre procese prin FIFO ......................................................... 73
3.3. INTERPRETOARE ALE FIIERELOR DE COMENZI ....................................................... 77
3.3.1.
Funcionarea unui interpretor de comenzi shell ........................................... 77
3.3.2.
Programarea n shell ................................................................................... 78
3.4. PROBLEME PROPUSE ............................................................................................. 80
4.

BIBLIOGRAFIE GENERAL ................................................................................ 82

1. Algoritmic i programare
1.1. Cutri i sortri
1.1.1.

Cutri

Datele se afl n memoria intern, ntr-un ir de articole. Vom cuta un articol dup un
cmp al acestuia pe care l vom considera cheie de cutare. n urma procesului de cutare va
rezulta poziia elementului cutat (dac acesta exist).
Notnd cu k1, k2, ...., kn cheile corespunztoare articolelor i cu a cheia pe care o cutm,
problema revine la a gsi (dac exist) poziia p cu proprietatea a = kp.
De obicei articolele sunt pstrate n ordinea cresctoare a cheilor, deci vom presupune c
k1 < k2 < .... < kn .
Uneori este util s aflm nu numai dac exist un articol cu cheia dorit ci i s gsim n caz
contrar locul n care ar trebui inserat un nou articol avnd cheia specificat, astfel nct s se
pstreze ordinea existent.
Deci problema cutrii are urmtoarea specificare:
Date a,n,(ki, i=1,n);
Precondiia: nN, n1 i k1 < k2 < .... < kn ;
Rezultate p;
Postcondiia: (p=1 i a k1) sau (p=n+1 i a > kn) sau (1<pn) i (kp-1 < a kp).

1.1.1.1.

Cutare secvenial

O prim metod este cutarea secvenial, n care sunt examinate succesiv toate cheile.
Sunt deosebite trei cazuri: ak1, a>kn, respectiv k1 < a kn, cutarea avnd loc n al treilea caz.
{nN, n1 i k1 < k2 < .... < kn}
{Se caut p astfel ca: (p=1 i a k1) sau}
{ (p=n+1 i a>kn) sau (1<pn) i (kp-1 < a kp).
{Cazul "nc negasit"}

Subalgoritmul CautSecv(a, n, K, p) este:

Fie p := 0;
Dac a k1 atunci p := 1 altfel
Dac a > kn atunci p := n + 1 altfel
Pentru i := 2; n execut
Dac (p = 0) i (a ki) atunci p := i sfdac
sfpentru

sfdac
sfdac
sf-CautSecv

Se observ c prin aceast metod se vor executa n cel mai nefavorabil caz n-1 comparri,
ntruct contorul i va lua toate valorile de la 2 la n. Cele n chei mpart axa real n n+1 intervale.
Tot attea comparri se vor efectua n n-1 din cele n+1 intervale n care se poate afla cheia
cutat, deci complexitatea medie are acelai ordin de mrime ca i complexitatea n cel mai ru
caz.
Evident c n multe situaii acest algoritm face calcule inutile. Atunci cnd a fost deja
gsit cheia dorit este inutil a parcurge ciclul pentru celelalte valori ale lui i. Cu alte cuvinte
este posibil s nlocuim ciclul PENTRU cu un ciclu CTTIMP. Ajungem la un al doilea
algoritm, dat n continuare.
{nN, n1 i k1 < k2 < .... < kn}
{Se caut p astfel ca: p=1 i a k1) sau }
{(p=n+1 i a>kn) sau (1<pn) i (kp-1 < a kp).

Subalgoritmul CautSucc(a, n, K, p) este:

Fie p:=1;
Dac a>k1 atunci
Cttimp pn i a>kp execut p:=p+1 sfct
sfdac
sf-CautSucc

n cel mai ru caz i acest algoritm face acelai numr de operaii ca i subalgoritmul
Cautsecv. n medie numrul operaiilor este jumtate din numrul mediu de operaii efecuat de
subalgoritmul Cautsecv deci complexitatea este aceeai.

1.1.1.2.

Cutare binar

O alt metod, numit cutare binar, care este mult mai eficient, utilizeaz tehnica
"divide et impera" privitor la date. Se determin n ce relaie se afl cheia articolului aflat n
mijlocul coleciei cu cheia de cutare. n urma acestei verificri cutarea se continu doar ntr-o
jumtate a coleciei. n acest mod, prin njumtiri succesive se micoreaz volumul coleciei
rmase pentru cutare. Cutarea binar se poate realiza practic prin apelul funciei
BinarySearch(a, n, K, 1, n), descris mai jos, folosit n subalgoritmul dat n continuare.
{nN, n1 i k1 < k2 < .... < kn}
{Se caut p astfel ca: (p=1 i a k1) sau}
{(p=n+1 i a>kn) sau (1<pn) i (kp-1 < a kp)}

Subalgoritmul CautBin(a, n, K, p) este:

Dac a k1 atunci p := 1 altfel


Dac a > kn atunci p := n+1 altfel
P := BinarySearch(a, n, K, 1, n)
sfdac
sfdac
sf-CautBin

Funcia BinarySearch(a, n, K, St, Dr) este:


Dac St Dr - 1
atunci BinarySearch := Dr
altfel m := (St+Dr) Div 2;
Dac a km
atunci BinarySearch := BinarySearch(a, n, K, St, m)
altfel BinarySearch := BinarySearch(a, n, K, m, Dr)
sfdac
sfdac
sf-BinarySearch

n funcia BinarySearch descris mai sus, variabilele St i Dr reprezint capetele


intervalului de cutare, iar m reprezint mijlocul acestui interval. Prin aceast metod, ntr-o
colecie avnd n elemente, rezultatul cutrii se poate furniza dup cel mult log2n comparri.
Deci complexitatea n cel mai ru caz este direct proporional cu log2n. Fr a insista asupra
demonstraiei, menionm c ordinul de mrime al complexitii medii este acelai.
Se observ c funcia BinarySearch se apeleaz recursiv. Se poate nltura uor
recursivitatea, aa cum se poate vedea n urmtoarea funcie:
Funcia BinSeaNerec(a, n, K, St, Dr) este:
Cttimp Dr St > 1 execut
m := (St+Dr) Div 2;
Dac a km atunci Dr := m altfel St := m sfdac
sfct
BinSeaNerec := Dr
sf-BinSeaNerec

1.1.2.

Sortri

Prin sortare intern vom nelege o rearanjare a unei colecii aflate n memoria intern
astfel nct cheile articolelor s fie ordonate cresctor (eventual descresctor).
Din punct de vedere al complexitii algoritmilor problema revine la ordonarea cheilor.
Deci specificarea problemei de sortare intern este urmtoarea:
Date n,K;

{K=(k1,k2,...,kn)}

Precondiia: kiR, i=1,n


Rezultate K';
Postcondiia: K' este o permutare a lui K, dar ordonat cresctor.
Deci k1 k2 ... kn.

1.1.2.1.

Sortare prin selecie

O prim tehnic numit "Selecie" se bazeaz pe urmtoarea idee: se determin poziia


elementului cu cheie de valoare minim (respectiv maxim), dup care acesta se va interschimba
cu primul element. Acest procedeu se repet pentru subcolecia rmas, pn cnd mai rmne
doar elementul maxim.
Subalgoritmul Selectie(n, K) este:

{Se face o permutare a celor}


{n componente ale vectorului K astfel}
{ca k1 k2 .... kn }

Pentru i := 1; n-1 execut


Fie ind := i;
Pentru j := i + 1; n execut
Dac kj < kind atunci ind := j sfdac
sfpentru
Dac i < ind atunci t := ki; ki := kind; kind := t sfdac
sfpentru
sf-Selectie

Se observ c numrul de comparri este:


(n-1)+(n-2)+...+2+1=n(n-1)/2
indiferent de natura datelor. Deci complexitatea medie, dar i n cel mai ru caz este O(n2).

1.1.2.2.

Bubble sort

Metoda "BubbleSort", compar dou cte dou elemente consecutive iar n cazul n care
acestea nu se afl n relaia dorit, ele vor fi interschimbate. Procesul de comparare se va ncheia
n momentul n care toate perechile de elemente consecutive sunt n relaia de ordine dorit.
Subalgoritmul BubbleSort(n, K) este:
Repet
Fie kod := 0;
Pentru i := 2; n execut
Dac ki-1 > ki atunci
t := ki-1;
ki-1 := ki;
ki := t;
kod := 1
sfdac
sfpentru
pncnd kod = 0 sfrep
sf-BubbleSort

{Ipoteza "este ordine"}

{N-a fost ordine!}

{Ordonare}

Acest algoritm execut n cel mai nefavorabil caz (n-1)+(n-2)+ ... +2+1 = n(n-1)/2
comparri, deci complexitatea lui este O(n2).
O variant optimizat a algoritmului "BubbleSort" este :

Subalgoritmul BubbleSort(n, K) este:


Fie s := 0
Repet
Fie kod := 0;
Pentru i := 2; n-s execut
Dac ki-1 > ki atunci
t := ki-1;
ki-1 := ki;
ki := t;
kod := 1
sfdac
sfpentru
s := s + 1
pncnd kod = 0 sfrep
sf-BubbleSort

1.1.2.3.

{Ipoteza "este ordine"}

{N-a fost ordine!}

{Ordonare}

Quicksort

O metod mai performant de ordonare, care va fi prezentat n continuare, se numete


"QuickSort" i se bazeaz pe tehnica "divide et impera" dup cum se poate observa n
continuare. Metoda este prezentat sub forma unei proceduri care realizeaz ordonarea unui
subir precizat prin limita inferioar i limita superioar a indicilor acestuia. Apelul procedurii
pentru ordonarea ntregului ir este : QuickSort(n, K, 1, n), unde n reprezint numrul de
articole ale coleciei date. Deci
Subalgoritmul SortareRapid(n, K) este:
Cheam QuickSort(n, K, 1, n)
sf-SortareRapid

Procedura QuickSort(n, K, St, Dr) va realiza ordonarea subirului kSt, kSt+1, ...,
kDr. Acest subir va fi rearanjat astfel nct kSt s ocupe poziia lui final (cnd irul este
ordonat). Dac i este aceast poziie, irul va fi rearanjat astfel nct urmtoarea condiie s fie
ndeplinit:
kj ki kl , pentru st j < i < l dr

(*)

Odat realizat acest lucru, n continuare va trebui doar s ordonm subirul kSt, kSt+1,
... ,ki-1 prin apelul recursiv al procedurii QuickSort(n, K, St, i-1) i apoi subirul ki+1,
...,kDr prin apelul QuickSort(n, K, i+1, Dr). Desigur ordonarea acestor dou subiruri
(prin apelul recursiv al procedurii) mai este necesar doar dac acestea conin cel puin dou
elemente.
Procedura QuickSort este prezentat n continuare :

Subalgoritmul QuickSort (n, K, St, Dr) este:


Fie i := St; j := Dr; a := ki;
Repet
Cttimp kj a i (i < j) execut j := j - 1 sfct
ki := kj;
Cttimp ki a i (i < j) execut i := i + 1 sfct
kj := ki ;
pncnd i = j sfrep
Fie ki := a;
Dac St < i-1 atunci Cheam QuickSort(n, K, St, i - 1) sfdac
Dac i+1 < Dr atunci Cheam QuickSort(n, K, i + 1, Dr) sfdac
sf-QuickSort

Complexitatea algoritmului prezentat este O(n2) n cel mai nefavorabil caz, dar
complexitatea medie este de ordinul O(nlog2n).

1.2. Metodele backtracking i divide et impera

1.2.1.

Metoda backtracking

Metoda backtracking (cutare cu revenire) este aplicabil in general unor probleme ce au


mai multe soluii.
Vom considera nti un exemplu, dup care vom indica civa algoritmi generali pentru
aceast metod.
Problema 1. (Generarea permutrilor) Fie n un numr natural. Determinai permutrile
numerelor 1, 2, ..., n.
O soluie pentru generarea permutrilor, n cazul particular n = 3, ar putea fi:
Subalgoritmul Permutri1 este:
Pentru i1 := 1; 3 execut
Pentru i2 := 1; 3 execut
Pentru i3 := 1; 3 execut
Fie posibil := (i1, i2, i3)
Dac componentele vectorului posibil sunt distincte
atunci
Tiprete posibil
sfdac
sfpentru
sfpentru
sfpentru
sf-Permutri1

x1
x2

x3 1

Figura 1.1. Reprezentare grafic a produsului cartezian {1, 2, 3}3


Observaii privind subalgoritmul Permutri1:
Pentru n oarecare nu putem descrie un algoritm care s conin n cicluri n textul
surs.
Numrul total de vectori verificai este 33, iar n general nn. Vectorii posibil verificai
sunt reprezentai grafic n Figura 1.1 - fiecare vector este un drum de la rdcin (de sus)
spre frunze (baza arborelui).
Algoritmul atribuie valori tuturor componentelor vectorului x, apoi verific dac
vectorul este o permutare.
O mbuntire a acestor algoritmi ar consta n a verifica anumite condiii din problem n
timp ce se construiesc vectorii, evitnd completarea inutil a unor componente.
De exemplu, dac prima component a vectorului construit (posibil) este 1, atunci este
inutil s atribuim celei de a doua componente valoarea 1, iar componentei a treia oricare din
valorile 1, 2 sau 3. Dac n este mare se evit completarea multor vectori ce au prefixul (1, ...). n
acest sens, (1, 3, ...) este un vector promitor (pentru a fi o permutare), n schimb vectorul (1, 1,
...) nu este. Vectorul (1, 3, ...) satisface anumite condiii de continuare (pentru a ajunge la
soluie) - are componente distincte. Nodurile haurate din Figura 1.1 constituie valori care nu
conduc la o soluie.
Vom descrie un algoritm general pentru metoda Bactracking dup care vom particulariza
acest algoritm pentru problemele enunate la nceputul seciunii. Pentru nceput vom face cteva
observaii i notaii privind metoda Backtracking aplicat unei probleme n care soluiile se
reprezint pe vectori, nu neaprat de lungime fix:
spaiul de cutare a soluiilor (spaiul soluiilor posibile): S = S1 x S2 x ... x Sn;
posibil este vectorul pe care se reprezint soluiile;
posibil[1..k] S1 x S2 x ... x Sk este un vector care poate conduce sau nu la o soluie; k
reprezint indice pentru vectorul posibil, respectiv nivel n arborele care red grafic procesul
de cutare (Figura 1.2).
posibil[1..k] este promitor dac satisface condiii care pot conduce la o soluie;
soluie(n, k, posibil) funcie care verific dac vectorul (promitor) posibil[1..k] este soluie
a problemei.
9

Start
x1

x1

...

...
x2
x3
...

...

... n

... n

...

... n

...

...

xk-1

...

xk

. . . xk

xk+1
...
xn

Cautare pe nivel

x2

...

. . . x3 . . . n
...
xk-1

... n

... n

...

... n

...
xk-1+1

xk+1

... n

... i ... n

Cautare cu succes:
1
pas in fata

Cautare fara succes:


revenire pe nivelul k-1

...
n

Figura 1.2. Spaiul soluiilor posibile pentru generarea permutrilor


Procesul de cutare poate fi urmrit n algoritmul care urmeaz:
Algoritmul Backtracking este:
{varianta nefinisat}
Fie k := 1
@Iniializeaz cutarea pe nivelul k (= 1)
Cttimp k > 0 execut
{posibil[1..k-1] este promitor}
@Caut (secvenial) pe nivelul k o valoare v, pentru a completa n
continuare vectorul posibil[1..k-1] astfel nct posibil[1..k] s
fie promitor
Dac cutarea este cu succes
atunci Fie posibil[k] := v
{posibil[1..k] este promitor}
Dac soluie(n, k, posibil)
atunci
{o soluie! (rmnem pe nivelul k)}
Tiparete posibil[1..k]
altfel
{e doar un vector promitor}
@Initializeaza cautarea pe nivelul k+1
Fie k := k + 1
{pas n fa (pe nivelul k+1)}
sfdac
altfel
{pas n spate (revenire pe nivelul k-1)}
k := k - 1
sfdac
sfct
sf-Backtracking

Pentru a finisa acest algoritm trebuie s precizm elementele nestandard prezente. Astfel,
avem nevoie de funcia boolean
condiii-continuare(k, posibil, v)

funcie care verific dac vectorul promitor posibil[1..k-1] completat cu valoarea v conduce la
un vector promitor.
Apoi, pentru a iniializa cutarea la nivelul j avem nevoie de a alege un element fictiv din
mulimea Sj, activitate realizat de funcia
10

init(j)

care returneaz acest element fictiv, care are rolul de a indica faptul c din mulimea S nc nu sa ales nici un element, deci dup el urmeaz primul element propriu din aceast mulime. Pentru
a cuta o valoare pe nivelul j, n ipoteza c valoarea curent nu e bun, avem nevoie de funcia
boolean
urmtor(j, v, nou)

care este True dac poate alege o valoare din Sj care urmeaz dup valoarea v, valoare notat
prin nou i False n cazul n care nu mai exist alte valori n Sj, deci nu mai poate fi fcut
alegerea. Cu aceste notaii algoritmul devine:
Algoritmul Backtracking este:
{versiune final}
Fie k := 1;
posibil[1] := init(1);
Cttimp k > 0 execut
{posibil[1..k-1] este promitor}
Fie Gsit := false; v := posibil[k] ;
Cttimp Urmtor(k, v,urm) i not Gsit execut
Fie v := urm;
Dac condiii-continuare(k, posibil, v) atunci
Gsit := true
sfdac
sfct
Dac Gsit
atunci Fie posibil[k] := v;
{posibil[1..k] este promitor}
Dac soluie(n, k, posibil)
atunci
{o soluie! (rmnem pe nivelul k)}
Tiparete posibil[1..k]
altfel
{e doar un vector promitor}
Fie k := k + 1;
{pas n fa (pe nivelul k+1)}
posibil[k] := init(k)
sfdac
altfel
{pas n spate (revenire pe nivelul k-1)}
k := k - 1;
sfdac
sfct
sf-Backtracking

Procesul de cutare a unei valori pe nivelul k i funciile condiii-continuare i soluie sunt


dependente de problema care se rezolv. De exemplu, pentru generarea permutrilor funciile
menionate sunt:
Funcia init(k) este:
Init := 0
sf-init;
Funcia Urmtor(k, v, urm) este:
Dac v < n
atunci Urmtor := True; urm := v + 1
altfel Urmtor := False
sfdac
sf-urmator
Funcia conditii-continuare(k, posibil, v) este:
Kod := True;
i := 1;
Cttimp kod i (i < k) execut
Dac posibil[i] = v atunci kod := False sfdac
i := i + 1;

11

sfct
conditii-continuare:=kod
sf-conditii
Funcia soluii(n, k, posibil) este:
Soluii := (k = n)
sf-solutii

n ncheiere, menionm c explorarea backtracking poate fi descris de asemenea recursiv. Dm


n acest scop urmtoru subalgoritm:
Subalgoritmul Backtracking(k, posibil)

este:

{posibil[1..k] este promitor}


Dac soluie(n, k, posibil) atunci
{o soluie! terminare apel recursiv, astfel}
Tipareste posibil[1..k]
{rmnem pe acelai nivel}
altfel
Pentru fiecare v valoare posibil pentru posibil[k+1] execut
Dac condiii-continuare(k + 1, posibil, v) atunci
posibil[k + 1] := v
Backtracking(k + 1, posibil)
{pas in fa}
sfdac
sfpentru
sfdac
{terminare apel Backtracking(k, posibil)}
sf-Backtracking {deci, pas n spate (revenire)}

cu apelul iniial Cheam Backtracking(0, posibil).

1.2.2.

Metoda "divide et impera"

Strategia "Divide et Impera" n programare presupune mparirea datelor ("divide and


conquer") i mpartirea problemei n subprobleme ("top-down").
Metoda se aplica problemelor care pot fi descompuse n subprobleme independente,
similar problemei iniiale, de dimensiuni mai mici i care pot fi rezolvabile foarte uor. Ea se
aplic atunci cnd rezolvarea problemei P pentru setul de date D se poate face prin rezolvarea
aceleiai probleme P pentru alte seturi de date d1, d2, ..., dk de volum mai mic dect D.
Observaii:
o mprirea se face pn cnd se obine o problem rezolvabil imediat.
o Subproblemele n care se descompune problema iniial trebuie s fie
independente. Dac subproblemele nu sunt independente, se aplic alte
metode de rezolvare.
o Tehnica admite i o implementare recursiv.

12

Metoda poate fi descris n felul urmtor:

mparte: Dac dimensiunea datelor este prea mare pentru a fi rezolvabil imediat,
mparte problema n una sau mai multe subprobleme independente (similare
problemei iniiale).

Stpnete: Folosete recursive aceeai metod pentru a rezolva subproblemele.

Combin: Combin soluiile subproblemelor pentru a obine soluia problemei


iniiale.

Subalgoritmul S pentru rezolvarea problemei P folosind metoda Divide et Impera are


urmtoarea structur:
Sublalgoritmul S(D) este:
Dac dim(D) a atunci
@problema se rezolva
altfel
@ Descompune D in d1, d2,..., dk
Cheama S(d1)
Cheama S(d2)
.
.
Cheama S(dk)
@ construieste rezultatul final prin utilizarea rezultatelor
partiale din apelurile de mai sus
sfdac
sf-NumeAlg

1.3. Concepte OOP n limbaje de programare

1.3.1.

1.3.1.1.

Noiunea de clas

Realizarea proteciei datelor prin metoda programrii modulare

Dezvoltarea programelor prin programare procedural nseamn folosirea unor funcii i


proceduri pentru scrierea programelor. n limbajul C lor le corespund funciile care
returneaz o valoare sau nu. ns n cazul aplicaiilor mai mari ar fi de dorit s putem realiza
i o protecie corespunztoare a datelor. Acest lucru ar nsemna c numai o parte a funciilor
s aib acces la datele problemei, acelea care se refer la datele respective. Programarea
modular ofer o posibilitate de realizare a proteciei datelor prin folosirea clasei de memorie
static. Dac ntr-un fiier se declar o dat aparinnd clasei de memorie static n afara
funciilor, atunci ea poate fi folosit ncepnd cu locul declarrii pn la sfritul modulului
respectiv, dar nu i n afara lui.
S considerm urmtorul exemplu simplu referitor la prelucrarea vectorilor de numere
ntregi. S se scrie un modul referitor la prelucrarea unui vector cu elemente ntregi, cu
13

funcii corespunztoare pentru iniializarea vectorului, eliberarea zonei de memorie ocupate i


ridicarea la ptrat, respectiv afiarea elementelor vectorului. O posibilitate de implementare a
modulului este prezentat n fiierul vector1.cpp:
#include <iostream>
using namespace std;
static int* e;
static int d;

//elementele vectorului
//dimensiunea vectorului

void init(int* e1, int d1) //initializare


{
d = d1;
e = new int[d];
for(int i = 0; i < d; i++)
e[i] = e1[i];
}
void distr()
{
delete [] e;
}

//eliberarea zonei de memorie ocupata

void lapatrat()
//ridicare la patrat
{
for(int i = 0; i < d; i++)
e[i] *= e[i];
}
void afiseaza()
//afisare
{
for(int i = 0; i < d; i++)
cout << e[i] << ' ';
cout << endl;
}

Modulul se compileaz separat obinnd un program obiect. Un exemplu de program


principal este prezentat n fiierul vector2.cpp:
extern void init( int*, int); //extern poate fi omis
extern void distr();
extern void lapatrat();
extern void afiseaza();
//extern int* e;
int main() {
int x[5] = {1, 2, 3, 4, 5};
init(x, 5);
lapatrat();
afiseaza();
distr();
int y[] = {1, 2, 3, 4, 5, 6};
init(y, 6);
//e[1]=10;
eroare, datele sunt protejate
lapatrat();
afiseaza();
distr();
return 0;
}

14

Observm c dei n programul principal se lucreaz cu doi vectori nu putem s-i folosim
mpreun, deci de exemplu modulul vector1.cpp nu poate fi extins astfel nct s realizeze i
adunarea a doi vectori. n vederea nlturrii acestui neajuns s-au introdus tipurile abstracte
de date.

1.3.1.2.

Tipuri abstracte de date

Tipurile abstracte de date realizeaz o legtur mai strns ntre datele problemei i operaiile
(funciile) care se refer la aceste date. Declararea unui tip abstract de date este asemntoare
cu declararea unei structuri, care n afar de date mai cuprinde i declararea sau definirea
funciilor referitoare la acestea.
De exemplu n cazul vectorilor cu elemente numere ntregi putem declara tipul abstract:
struct vect {
int* e;
int d;
void init(int* e1, int d1);
void distr() { delete [] e; }
void lapatrat();
void afiseaza();
};

Funciile declarate sau definite n interiorul structurii vor fi numite funcii membru iar datele
date membru. Dac o funcie membru este definit n interiorul structurii (ca i funcia distr
din exemplul de mai sus), atunci ea se consider funcie inline. Dac o funcie membru se
definete n afara structurii, atunci numele funciei se va nlocui cu numele tipului abstract
urmat de operatorul de rezoluie (::) i numele funciei membru. Astfel funciile init, lapatrat
i afiseaza vor fi definite n modul urmtor:
void vect::init(int *e1, int d1)
{
d = d1;
e = new int[d];
for(int i = 0; i < d; i++)
e[i] = e1[i];
}
void vect::lapatrat()
{
for(int i = 0; i < d; i++)
e[i] *= e[i];
}
void vect::afiseaza()
{
for(int i = 0; i < d; i++)
cout << e[i] << ' ';
cout << endl;
}

15

Dei prin metoda de mai sus s-a realizat o legtur ntre datele problemei i funciile
referitoare la aceste date, ele nu sunt protejate, deci pot fi accesate de orice funcie utilizator,
nu numai de funciile membru. Acest neajuns se poate nltura cu ajutorul claselor.

1.3.1.3.

Declararea claselor

Un tip abstract de date clas se declar ca i o structur, dar cuvntul cheie struct se
nlocuiete cu class. Ca i n cazul structurilor referirea la tipul de dat clas se face cu
numele dup cuvntul cheie class (numele clasei). Protecia datelor se realizeaz cu
modificatorii de protecie: private, protected i public. Dup modificatorul de protecie se
pune caracterul :. Modificatorul private i protected reprezint date protejate, iar public date
neprotejate. Domeniul de valabilitate a modificatorilor de protecie este pn la urmtorul
modificator din interiorul clasei, modificatorul implicit fiind private. Menionm c i n
cazul structurilor putem s folosim modificatori de protecie, dar n acest caz modificatorul
implicit este public.
De exemplu clasa vector se poate declara n modul urmtor:
class vector {
int* e; //elementele vectorului
int d;
//dimensiunea vectorului
public:
vector(int* e1, int d1);
~vector() { delete [] e; }
void lapatrat();
void afiseaza();
};

Se observ c datele membru e i d au fost declarate ca date de tip private (protejate), iar
funciile membru au fost declarate publice (neprotejate). Bineneles, o parte din datele
membru pot fi declarate publice, i unele funcii membru pot fi declarate protejate, dac
natura problemei cere acest lucru. n general, datele membru protejate pot fi accesate numai
de funciile membru ale clasei respective i eventual de alte funcii numite funcii prietene
(sau funcii friend).
O alt observaie important referitoare la exemplul de mai sus este c iniializarea datelor
membru i eliberarea zonei de memorie ocupat s-a fcut prin funcii membru specifice.
Datele declarate cu ajutorul tipului de dat clas se numesc obiectele clasei, sau simplu
obiecte. Ele se declar n mod obinuit n forma:
nume_clas list_de_obiecte;

De exemplu, un obiect de tip vector se declar n modul urmtor:


vector v;

Iniializarea obiectelor se face cu o funcie membru specific numit constructor. n cazul


distrugerii unui obiect se apeleaz automat o alt funcie membru specific numit destructor.
n cazul exemplului de mai sus

16

vector(int* e1, int d1);

este un constructor, iar


~vector() { delete [] e; }

este un destructor.
Tipurile abstracte de date de tip struct pot fi i ele considerate clase cu toate elementele
neprotejate. Constructorul de mai sus este declarat n interiorul clasei, dar nu este definit, iar
destructorul este definit n interiorul clasei. Rezult c destructorul este o funcie inline.
Definirea funciilor membru care sunt declarate, dar nu sunt definite n interiorul clasei se
face ca i n cazul tipurilor abstracte de date de tip struct, folosind operatorul de rezoluie.

1.3.1.4.

Membrii unei clase. Pointerul this

Referirea la datele respectiv funciile membru ale claselor se face cu ajutorul operatorilor
punct (.) sau sgeat (->) ca i n cazul referirii la elementele unei structuri. De exemplu, dac
se declar:
vector v;
vector* p;

atunci afiarea vectorului v respectiv a vectorului referit de pointerul p se face prin:


v.afiseaza();
p->afiseaza();

n interiorul funciilor membru ns referirea la datele respectiv funciile membru ale clasei se
face simplu prin numele acestora fr a fi nevoie de operatorul punct (.) sau sgeat (->). De
fapt compilatorul genereaz automat un pointer special, pointerul this, la fiecare apel de
funcie membru, i folosete acest pointer pentru identificarea datelor i funciilor membru.
Pointerul this va fi declarat automat ca pointer ctre obiectul curent. n cazul exemplului de
mai sus pointerul this este adresa vectorului v respectiv adresa referit de pointerul p.
Dac n interiorul corpului funciei membru afiseaza se utilizeaz de exemplu data membru d,
atunci ea este interpretat de ctre compilator ca i this->d.
Pointerul this poate fi folosit i n mod explicit de ctre programator, dac natura problemei
necesit acest lucru.

1.3.1.5.

Constructorul

Iniializarea obiectelor se face cu o funcie membru specific numit constructor. Numele


constructorului trebuie s coincid cu numele clasei. O clas poate s aib mai muli
constructori. n acest caz aceste funcii membru au numele comun, ceea ce se poate face
datorit posibilitii de suprancrcare a funciilor. Bineneles, n acest caz numrul i/sau
17

tipul parametrilor formali trebuie s fie diferit, altfel compilatorul nu poate s aleag
constructorul corespunztor.
Constructorul nu returneaz o valoare. n acest caz nu este permis nici folosirea cuvntului
cheie void.
Prezentm n continuare un exemplu de tip clasa cu mai muli constructori, avnd ca date
membru numele i prenumele unei persoane, i cu o funcie membru pentru afiarea numelui
complet.
Fiierul persoana.h:
class persoana {
char* nume;
char* prenume;
public:
persoana();
persoana(char* n, char* p);
persoana(const persoana& p1);
~persoana();
void afiseaza();
};

//constructor implicit
//constructor
//constructor de copiere
//destructor

Fiierul persoana.cpp:
#include <iostream>
#include <cstring>
#include "persoana.h"
using namespace std;
persoana::persoana()
{
nume = new char[1];
*nume = 0;
prenume = new char[1];
*prenume = 0;
cout << "Apelarea constructorului implicit." << endl;
}
persoana::persoana(char* n, char* p)
{
nume = new char[strlen(n)+1];
prenume = new char[strlen(p)+1];
strcpy(nume, n);
strcpy(prenume, p);
cout << "Apelare constructor (nume, prenume).\n";
}
persoana::persoana(const persoana& p1)
{
nume = new char[strlen(p1.nume)+1];
strcpy(nume, p1.nume);
prenume = new char[strlen(p1.prenume)+1];
strcpy(prenume, p1.prenume);
cout << "Apelarea constructorului de copiere." << endl;
}

18

persoana::~persoana()
{
delete[] nume;
delete[] prenume;
}
void persoana::afiseaza()
{
cout << prenume << ' ' << nume << endl;
}

Fiierul persoanaTest.cpp:
#include "persoana.h"
int main() {
persoana A;
//se apeleaza constructorul implicit
A.afiseaza();
persoana B("Stroustrup", "Bjarne");
B.afiseaza();
persoana *C = new persoana("Kernighan","Brian");
C->afiseaza();
delete C;
persoana D(B);
//echivalent cu persoana D = B;
//se apeleaza constructorul de copire
D.afiseaza();
return 0;
}

Observm prezena a doi constructori specifici: constructorul implicit i constructorul de


copiere. Dac o clas are constructor fr parametri atunci el se va numi constructor implicit.
Constructorul de copiere se folosete la iniializarea obiectelor folosind un obiect de acelai
tip (n exemplul de mai sus o persoan cu numele i prenumele identic). Constructorul de
copiere se declar n general n forma:
nume_clas(const nume_clas& obiect);

Cuvntul cheie const exprim faptul c argumentul constructorului de copiere nu se modific.


O clas poate s conin ca date membru obiecte ale unei alte clase. Declarnd clasa sub
forma:
class nume_clasa {
nume_clasa_1 ob_1;
nume_clasa_2 ob_2;
...
nume_clasa_n ob_n;
...
};

antetul constructorului clasei nume_clasa va fi de forma:


nume_clasa(lista_de_argumente):
ob_1(l_arg_1), ob_2(l_arg_2), ..., ob_n(l_arg_n)

19

unde lista_de_argumente respectiv l_arg_i reprezint lista parametrilor formali ai


constructorului clasei nume_clasa respectiv ai obiectului ob_i.
Din lista ob_1(l_arg_1), ob_2(l_arg_2), ..., ob_n(l_arg_n) pot s lipseasc
obiectele care nu au constructori definii de programator, sau obiectul care se iniializeaz cu
un constructor implicit, sau cu toi parametrii implicii.
Dac clasa conine date membru de tip obiect atunci se vor apela mai nti constructorii
datelor membru, iar dup aceea corpul de instruciuni al constructorului clasei respective.
Fiierul pereche.cpp:
#include <iostream>
#include "persoana.h"
using namespace std;
class pereche {
persoana sot;
persoana sotie;
public:
pereche()
//definitia constructorului implicit
{
//se vor apela constructorii impliciti
}
//pentru obiectele sot si sotie
pereche(persoana& sotul, persoana& sotia);
pereche(char* nume_sot, char* prenume_sot,
char* nume_sotie, char* prenume_sotie):
sot(nume_sot, prenume_sot),
sotie(nume_sotie, prenume_sotie)
{
}
void afiseaza();
};
inline pereche::pereche(persoana& sotul, persoana& sotia):
sot(sotul), sotie(sotia)
{
}
void pereche::afiseaza()
{
cout << "Sot: ";
sot.afiseaza();
cout << "Sotie: ";
sotie.afiseaza();
}
int main() {
persoana A("Pop", "Ion");
persoana B("Popa", "Ioana");
pereche AB(A, B);
AB.afiseaza();
pereche CD("C","C","D","D");
CD.afiseaza();
pereche EF;

20

EF.afiseaza();
return 0;
}

Observm c n cazul celui de al doilea constructor, parametrii formali sot i sotie au fost
declarai ca i referine la tipul persoana. Dac ar fi fost declarai ca parametri formali de tip
persoana, atunci n cazul declaraiei:
pereche AB(A, B);

constructorul de copiere s-ar fi apelat de patru ori. n astfel de situaii se creeaz mai nti
obiecte temporale folosind constructorul de copiere (dou apeluri n cazul de fa), dup care
se execut constructorii datelor membru de tip obiect (nc dou apeluri).

1.3.1.6.

Destructorul

Destructorul este funcia membru care se apeleaz n cazul distrugerii obiectului. Destructorul
obiectelor globale se apeleaz automat la sfritul funciei main ca parte a funciei exit. Deci,
nu este indicat folosirea funciei exit ntr-un destructor, pentru c acest lucru duce la un ciclu
infinit. Destructorul obiectelor locale se execut automat la terminarea blocului n care s-au
definit. n cazul obiectelor alocate dinamic, de obicei destructorul se apeleaz indirect prin
operatorul delete (obiectul trebuie s fi fost creat cu operatorul new). Exist i un mod
explicit de apelare a destructorului, n acest caz numele destructorului trebuie precedat de
numele clasei i operatorul de rezoluie.
Numele destructorului ncepe cu caracterul ~ dup care urmeaz numele clasei. Ca i n cazul
constructorului, destructorul nu returneaz o valoare i nu este permis nici folosirea
cuvntului cheie void. Apelarea destructorului n diferite situaii este ilustrat de urmtorul
exemplu. Fiierul destruct.cpp:
#include <iostream>
#include <cstring>
using namespace std;
class scrie { //scrie pe stdout ce face.
char* nume;
public:
scrie(char* n);
~scrie();
};
scrie::scrie(char* n)
{
nume = new char[strlen(n)+1];
strcpy(nume, n);
cout << "Am creat obiectul: " << nume << '\n';
}
scrie::~scrie()
{
cout << "Am distrus obiectul: " << nume << '\n';
delete nume;

21

}
void functie()
{
cout << "Apelare functie" << '\n';
scrie local("Local");
}
scrie global("Global");
int main() {
scrie* dinamic = new scrie("Dinamic");
functie();
cout << "Se continua programul principal" << '\n';
delete dinamic;
return 0;
}

1.4. Relaii ntre clase


1.4.1.

Bazele teoretice

Prin folosirea tipurilor abstracte de date, se creeaz un tot unitar pentru gestionarea datelor i
a operaiilor referitoare la aceste date. Cu ajutorul tipului abstract clas se realizeaz i
protecia datelor, deci n general elementele protejate nu pot fi accesate dect de funciile
membru ale clasei respective. Aceast proprietate a obiectelor se numete ncapsulare
(encapsulation).
n viaa de zi cu zi ns ne ntlnim nu numai cu obiecte separate, dar i cu diferite legturi
ntre aceste obiecte, respectiv ntre clasele din care obiectele fac parte. Astfel se formeaz o
ierarhie de clase. Rezult a doua proprietate a obiectelor: motenirea (inheritance). Acest
lucru nseamn c se motenesc toate datele i funciile membru ale clasei de baz de ctre
clasa derivat, dar se pot aduga elemente noi (date membru i funcii membru) n clasa
derivat. n cazul n care o clas derivat are mai multe clase de baz se vorbete despre
motenire multipl.
O alt proprietate important a obiectelor care aparin clasei derivate este c funciile membru
motenite pot fi suprancrcate. Acest lucru nseamn c o operaie referitoare la obiectele
care aparin ierarhiei are un singur identificator, dar funciile care descriu aceast operaie pot
fi diferite. Deci, numele funciei i lista parametrilor formali este aceeai n clasa de baz i n
clasa derivat, dar descrierea funciilor difer ntre ele. Astfel, n clasa derivat funciile
membru pot fi specifice clasei respective, dei operaia se identific prin acelai nume.
Aceast proprietate se numete polimorfism.

1.4.2.

Declararea claselor derivate

O clas derivat se declar n felul urmtor:

22

class nume_clas_derivat : lista_claselor_de_baz {


//date membru noi i funcii membru noi
};

unde lista_claselor_de_baz este de forma:


elem_1, elem_2, ..., elem_n

i elem_i pentru orice 1 i n poate fi


public clas_de_baz_i

sau
protected clas_de_baz_i

sau
private clas_de_baz_i

Cuvintele cheie public, protected i private se numesc i de aceast dat modificatori de


protecie. Ei pot s lipseasc, n acest caz modificatorul implicit fiind private. Accesul la
elementele din clasa derivat este prezentat n tabelul 1.

Accesul la
elementele din clasa
de baz

Modificatorii de
protecie referitoare
la clasa de baz

Accesul la
elementele din clasa
derivat

public

public

public

protected

public

protected

private

public

inaccesibil

public

protected

protected

protected

protected

protected

private

protected

inaccesibil

public

private

private

protected

private

private

private

private

inaccesibil

Tabelul 1: accesul la elementele din clasa derivat


Observm c elementele de tip private ale clasei de baz sunt inaccesibile n clasa derivat.
Elementele de tip protected i public devin de tip protected, respectiv private dac
modificatorul de protecie referitor la clasa de baz este protected respectiv private, i rmn
neschimbate dac modificatorul de protecie referitor la clasa de baz este public. Din acest
motiv n general datele membru se declar de tip protected i modificatorul de protecie
referitor la clasa de baz este public. Astfel datele membru pot fi accesate, dar rmn
protejate i n clasa derivat.

23

1.4.3.

Funcii virtuale

Noiunea de polimorfism ne conduce n mod firesc la problematica determinrii


funciei membru care se va apela n cazul unui obiect concret. S considerm urmtorul
exemplu. Declarm clasa de baz baza, i o clas derivat din acest clas de baz, clasa
derivata. Clasa de baz are dou funcii membru: functia_1 i functia_2. n interiorul funciei
membru functia_2 se apeleaz functia_1. n clasa derivat se suprancarc funcia membru
functia_1, dar funcia membru functia_2 nu se suprancarc. n programul principal se declar
un obiect al clasei derivate i se apeleaz funcia membru functia_2 motenit de la clasa de
baz. n limbajul C++ acest exemplu se scrie n urmtoarea form.
Fiierul virtual1.cpp:
#include <iostream>
using namespace std;
class baza {
public:
void functia_1();
void functia_2();
};
class derivata : public baza {
public:
void functia_1();
};
void baza::functia_1()
{
cout << "S-a apelat functia membru functia_1"
<< " a clasei de baza" << endl;
}
void baza::functia_2()
{
cout << "S-a apelat functia membru functia_2"
<< " a clasei de baza" << endl;
functia_1();
}
void derivata::functia_1()
{
cout << "S-a apelat functia membru functia_1"
<< " a clasei derivate" << endl;
}
int main() {
derivata D;
D.functia_2();
}

Prin execuie se obine urmtorul rezultat:


S-a apelat functia membru functia_2 a clasei de baza
S-a apelat functia membru functia_1 a clasei de baza

24

ns acest lucru nu este rezultatul dorit, deoarece n cadrul funciei main s-a apelat funcia
membru functia_2 motenit de la clasa de baz, dar funcia membru functia_1 apelat de
functia_2 s-a determinat nc n faza de compilare. n consecin, dei funcia membru
functia_1 s-a suprancrcat n clasa derivat nu s-a apelat funcia suprancrcat ci funcia
membru a clasei de baz.
Acest neajuns se poate nltura cu ajutorul introducerii noiunii de funcie membru virtual.
Dac funcia membru este virtual, atunci la orice apelare a ei, determinarea funciei membru
corespunztoare a ierarhiei de clase nu se va face la compilare ci la execuie, n funcie de
natura obiectului pentru care s-a fcut apelarea. Aceast proprietate se numete legare
dinamic, iar dac determinarea funciei membru se face la compilare, atunci se vorbete de
legare static.
Am vzut c dac se execut programul virtual1.cpp se apeleaz funciile membru
functia_1 i functia_2 ale clasei de baz. ns funcia membru functia_1 fiind suprancrcat
n clasa derivat, ar fi de dorit ca funcia suprancrcat s fie apelat n loc de cea a clasei de
baz.
Acest lucru se poate realiza declarnd functia_1 ca funcie membru virtual. Astfel, pentru
orice apelare a funciei membru functia_1, determinarea acelui exemplar al funciei membru
din ierarhia de clase care se va executa, se va face la execuie i nu la compilare. Ca urmare,
funcia membru functia_1 se determin prin legare dinamic.
n limbajul C++ o funcie membru se declar virtual n cadrul declarrii clasei respective n
modul urmtor: antetul funciei membru se va ncepe cu cuvntul cheie virtual.
Dac o funcie membru se declar virtual n clasa de baz, atunci suprancrcrile ei se vor
considera virtuale n toate clasele derivate ale ierarhiei.
n cazul exemplului de mai sus declararea clasei de baz se modific n felul urmtor.
class baza {
public:
virtual void functia_1();
void functia_2();
};

Rezultatul obinut prin execuie se modific astfel:


S-a apelat functia membru functia_2 a clasei de baza
S-a apelat functia membru functia_1 a clasei derivate

Deci, ntr-adevr se apeleaz funcia membru functia_1 a clasei derivate.


Prezentm n continuare un alt exemplu n care apare necesitatea introducerii funciilor
membru virtuale. S se defineasc clasa fractie referitoare la numerele raionale, avnd ca
date membru numrtorul i numitorul fraciei. Clasa trebuie s aib un constructor, valoarea
implicit pentru numrtor fiind zero iar pentru numitor unu, precum i dou funcii membru:
produs pentru a calcula produsul a dou fracii i inmulteste pentru nmulirea obiectului
curent cu fracia dat ca i parametru. De asemenea, clasa fractie trebuie s aib i o funcie
membru pentru afiarea unui numr raional. Folosind clasa fractie ca i clas de baz se va
defini clasa derivat fractie_scrie, pentru care se va suprancrca funcia produs, astfel nct
concomitent cu efectuarea nmulirii s se afieze pe stdout operaia respectiv. Funcia
inmulteste nu se va suprancrca, dar operaia efectuat trebuie s se afieze pe dispozitivul
standard de ieire i n acest caz. Fiierul fvirt1.cpp:
25

#include <iostream>
using namespace std;
class fractie {
protected:
int numarator;
int numitor;
public:
fractie(int numarator1 = 0, int numitor1 = 1);
fractie produs(fractie& r);
//calculeaza produsul a doua
//fractii, dar nu simplifica
fractie& inmulteste(fractie& r);
void afiseaza();
};
fractie::fractie(int numarator1, int numitor1)
{
numarator = numarator1;
numitor
= numitor1;
}
fractie fractie::produs(fractie& r)
{
return fractie(numarator * r.numarator, numitor * r.numitor);
}
fractie& fractie::inmulteste(fractie& q)
{
*this = this->produs(q);
return *this;
}
void fractie::afiseaza()
{
if ( numitor )
cout << numarator << " / " << numitor;
else
cerr << "Fractie incorecta";
}
class fractie_scrie: public fractie{
public:
fractie_scrie( int numarator1 = 0, int numitor1 = 1 );
fractie produs( fractie& r);
};
inline fractie_scrie::fractie_scrie(int numarator1, int numitor1) :
fractie(numarator1, numitor1)
{
}
fractie fractie_scrie::produs(fractie& q)
{
fractie r = fractie(*this).produs(q);
cout << "(";
this->afiseaza();
cout << ") * (";
q.afiseaza();
cout << ") = ";

26

r.afiseaza();
cout << endl;
return r;
}
int main()
{
fractie p(3,4), q(5,2), r;
r = p.inmulteste(q);
p.afiseaza();
cout << endl;
r.afiseaza();
cout << endl;
fractie_scrie p1(3,4), q1(5,2);
fractie r1, r2;
r1 = p1.produs(q1);
r2 = p1.inmulteste(q1);
p1.afiseaza();
cout << endl;
r1.afiseaza();
cout << endl;
r2.afiseaza();
cout << endl;
return 0;
}

Prin execuie se obine:


15
15
(3
15
15
15

/
/
/
/
/
/

8
8
4) * (5 / 2) = 15 / 8
8
8
8

Observm c rezultatul nu este cel dorit, deoarece operaia de nmulire s-a afiat numai o
singur dat, i anume pentru expresia r1 = p1.produs(q1). n cazul expresiei r2 =
p1.inmulteste(q1) ns nu s-a afiat operaia de nmulire. Acest lucru se datoreaz
faptului c funcia membru inmulteste nu s-a suprancrcat n clasa derivat. Deci s-a apelat
funcia motenit de la clasa fractie. n interiorul funciei inmulteste s-a apelat funcia
membru produs, dar deoarece aceast funcie membru s-a determinat nc n faza de
compilare, rezult c s-a apelat funcia referitoare la clasa fractie i nu cea referitoare la clasa
derivat fractie_scrie. Deci, afiarea operaiei s-a efectuat numai o singur dat.
Soluia este, ca i n exemplul anterior, declararea unei funcii membru virtuale, i anume
funcia produs se va declara ca funcie virtual. Deci declararea clasei de baz se modific n
felul urmtor:
class fractie {
protected:
int numarator;
int numitor;
public:
fractie(int numarator1 = 0, int numitor1 = 1);
virtual fractie produs(fractie& r); //calculeaza produsul a doua
//fractii, dar nu simplifica
fractie& inmulteste(fractie& r);

27

void afiseaza();
};

Dup efectuarea acestei modificri prin executarea programului obinem:


15
15
(3
(3
15
15
15

/
/
/
/
/
/
/

8
8
4) * (5 / 2) = 15 / 8
4) * (5 / 2) = 15 / 8
8
8
8

Deci, se observ c afiarea operaiei s-a fcut de dou ori, pentru ambele expresii. Funciile
virtuale, ca i alte funcii membru de fapt, nu trebuie neaprat suprancrcate n clasele
derivate. Dac nu sunt suprancrcate atunci se motenete funcia membru de la un nivel
superior.
Determinarea funciilor membru virtuale corespunztoare se face pe baza unor tabele
construite i gestionate n mod automat. Obiectele claselor care au funcii membru virtuale
conin i un pointer ctre tabela construit. De aceea gestionarea funciilor membru virtuale
necesit mai mult memorie i un timp de execuie mai ndelungat.

1.4.4.

Clase abstracte

n cazul unei ierarhii de clase mai complicate, clasa de baz poate avea nite proprieti
generale despre care tim, dar nu le putem defini numai n clasele derivate. De exemplu s
considerm ierarhia de clase din figura 1.
Observm c putem determina nite proprieti referitoare la clasele derivate. De exemplu
greutatea medie, durata medie de via i viteza medie de deplasare. Aceste proprieti se vor
descrie cu ajutorul unor funcii membru. n principiu i pentru clasa animal exist o greutate
medie, durat medie de via i vitez medie de deplasare. Dar aceste proprieti ar fi mult
mai greu de determinat i ele nici nu sunt importante pentru noi ntr-o generalitate de acest
fel. Totui pentru o tratare general ar fi bine, dac cele trei funcii membru ar fi declarate n
clasa de baz i definite n clasele derivate. n acest scop s-a introdus noiunea de funcie
membru virtual pur.

Figura 1. Ierarhie de clase referitoare la animale

28

Funcia virtual pur este o funcie membru care este declarat, dar nu este definit n clasa
respectiv. Ea trebuie definit ntr-o clas derivat. Funcia membru virtual pur se declar
n modul urmtor. Antetul obinuit al funciei este precedat de cuvntul cheie virtual, i
antetul se termin cu = 0. Dup cum arat numele i declaraia ei, funcia membru virtual
pur este o funcie virtual, deci selectarea exemplarului funciei din ierarhia de clase se va
face n timpul execuiei programului.
Clasele care conin cel puin o funcie membru virtual pur se vor numi clase abstracte.
Deoarece clasele abstracte conin funcii membru care nu sunt definite, nu se pot crea obiecte
aparinnd claselor abstracte. Dac funcia virtual pur nu s-a definit n clasa derivat atunci
i clasa derivat va fi clas abstract i ca atare nu se pot defini obiecte aparinnd acelei
clase.
S considerm exemplul de mai sus i s scriem un program, care referitor la un porumbel,
urs sau cal determin dac el este gras sau slab, rapid sau ncet, respectiv tnr sau btrn.
Afiarea acestui rezultat se va face de ctre o funcie membru a clasei animal care nu se
suprancarc n clasele derivate. Fiierul abstract1.cpp:
#include <iostream>
using namespace std;
class animal {
protected:
double greutate; // kg
double virsta;
// ani
double viteza;
// km / h
public:
animal( double g, double v1, double v2);
virtual double greutate_medie() = 0;
virtual double durata_de_viata_medie() = 0;
virtual double viteza_medie() = 0;
int gras() { return greutate > greutate_medie(); }
int rapid() { return viteza > viteza_medie(); }
int tanar()
{ return 2 * virsta < durata_de_viata_medie(); }
void afiseaza();
};
animal::animal( double g, double v1, double v2)
{
greutate = g;
virsta = v1;
viteza = v2;
}
void animal::afiseaza()
{
cout << ( gras() ? "gras, " : "slab, " );
cout << ( tanar() ? "tanar, " : "batran, " );
cout << ( rapid() ? "rapid" : "incet" ) << endl;
}
class porumbel : public animal {
public:
porumbel( double g, double v1, double v2):
animal(g, v1, v2) {}

29

double greutate_medie() { return 0.5; }


double durata_de_viata_medie() { return 6; }
double viteza_medie() { return 90; }
};

class urs: public animal {


public:
urs( double g, double v1, double v2):
animal(g, v1, v2) {}
double greutate_medie() { return 450; }
double durata_de_viata_medie() { return 43; }
double viteza_medie() { return 40; }
};
class cal: public animal {
public:
cal( double g, double v1, double v2):
animal(g, v1, v2) {}
double greutate_medie() { return 1000; }
double durata_de_viata_medie() { return 36; }
double viteza_medie() { return 60; }
};
int main() {
porumbel p(0.6, 1, 80);
urs u(500, 40, 46);
cal c(900, 8, 70);
p.afiseaza();
u.afiseaza();
c.afiseaza();
return 0;
}

Observm c dei clasa animal este clas abstract, este util introducerea ei, pentru c multe
funcii membru pot fi definite n clasa de baz i motenite fr modificri n cele trei clase
derivate.

1.4.5.

Interfee

n limbajul C++ nu s-a definit noiunea de interfa, care exist n limbajele Java sau C#. Dar
orice clas abstract, care conine numai funcii virtuale pure, se poate considera o interfa.
Bineneles, n acest caz nu se vor declara nici date membru n interiorul clasei. Clasa
abstract animal conine att date membru, ct i funcii membru nevirtuale, deci ea nu se
poate considera ca i un exemplu de interfa.
n continuare introducem o clas abstract Vehicul, care nu conine numai funcii membru
virtuale pure, i dou clase derivate din aceast clas abstract. Fiierul vehicul.cpp:
#include <iostream>
using namespace std;
class Vehicul
{
public:
virtual void Porneste() = 0;

30

virtual void Opreste() = 0;


virtual void Merge(int km) = 0;
virtual void Stationeaza(int min) = 0;
};
class Bicicleta : public Vehicul
{
public:
void Porneste();
void Opreste();
void Merge(int km);
void Stationeaza(int min);
};
void Bicicleta::Porneste() {
cout << "Bicicleta porneste." << endl;
}
void Bicicleta::Opreste() {
cout << "Bicicleta se opreste." << endl;
}
void Bicicleta::Merge(int km) {
cout << "Bicicleta merge " << km <<
" kilometri." << endl;
}
void Bicicleta::Stationeaza(int min) {
cout << "Bicicleta stationeaza " << min <<
" minute." << endl;
}
class Masina : public Vehicul
{
public:
void Porneste();
void Opreste();
void Merge(int km);
void Stationeaza(int min);
};
void Masina::Porneste() {
cout << "Masina porneste." << endl;
}
void Masina::Opreste() {
cout << "Masina se opreste." << endl;
}
void Masina::Merge(int km) {
cout << "Masina merge " << km <<
" kilometri." << endl;
}
void Masina::Stationeaza(int min) {
cout << "Masina stationeaza " << min <<
" minute." << endl;
}
void Traseu(Vehicul *v)
{
v->Porneste();
v->Merge(3);
v->Stationeaza(2);
v->Merge(2);
v->Opreste();
}

31

int main()
{
Vehicul *b = new Bicicleta;
Traseu(b);
Vehicul *m = new Masina;
Traseu(m);
delete m;
delete b;
}

n funcia main s-au declarat dou obiecte dinamice de tip Bicicleta, respectiv Masina, i n
acest fel, apelnd funcia Traseu obinem rezultate diferite, dei aceast funcie are ca
parametru formal numai un pointer ctre o clas abstract Vehicul.

1.5. Liste i dicionare


In cele ce urmeaz vom prezenta dou dintre containerele des folosite in programare i
anume listele i dicionarele. Vom specifica tipurile abstracte de date corespunztoare,
indicnd i specificnd operaiile caracteristice. Pentru fiecare operaie din interfaa unui tip
de date, vom da specificarea operaiei n limbaj natural, indicnd datele i precondiiile
operaiei (pre), precum i rezultatele i postcondiiile operaiei (post).

1.5.1.

Liste

In limbajul uzual cuvntul list refer o nirare, ntr-o anumit ordine, a unor
nume de persoane sau de obiecte, a unor date etc. Exemple de liste sunt multiple: list de
cumprturi, list de preuri, list de studeni, etc. Ordinea n list poate fi interpretat ca un
fel de legtur ntre elementele listei (dup prima cumprtur urmeaz a doua
cumprtur, dup a doua cumprtur urmeaz a treia cumprtur, etc) sau poate fi vzut
ca fiind dat de numrul de ordine al elementului n list (1-a cumprtur, a 2-a
cumprtur, etc). Tipul de date List care va fi definit n continuare permite
implementarea n aplicaii a acestor situaii din lumea real.
Ca urmare, o list o putem vedea ca pe o secven de elemente < l1 , l 2 ,.., l n > de un
acelai tip (TElement) aflate ntr-o anumit ordine, fiecare element avnd o poziie bine
determinat n cadrul listei. Ca urmare, poziia elementelor n cadrul listei este esenial,
astfel accesul, tergerea i adugarea se pot face pe orice poziie n list. Lista poate fi vzut
ca o colecie dinamic de elemente n care este esenial ordinea elementelor. Numrul n de
elemente din list se numete lungimea listei. O list de lungime 0 se va numi lista vid.
Caracterul de dinamicitate al listei este dat de faptul c lista i poate modifica n timp
lungimea prin adugri i tergeri de elemente n/din list.
In cele ce urmeaz, ne vom referi la listele liniare. O list liniar, este o structur care
fie este vid (nu are nici un element), fie
are un unic prim element;
are un unic ultim element;
fiecare element din list (cu excepia ultimului element) are un singur succesor;
fiecare element din list (cu excepia primului element) are un singur predecesor.

32

Ca urmare, ntr-o list liniar se pot insera elemente, terge elemente, se poate
determina succesorul (predecesorul) unui element, se poate accesa un element pe baza
poziiei sale n list.
O list liniar se numete circular dac se consider predecesorul primului nod a fi
ultimul nod, iar succesorul ultimului nod a fi primul nod.
Conform definiiei anterioare, fiecare element al unei listei liniare are o poziie bine
determinat n list. De asemenea, este important prima poziie n cadrul listei, iar dac se
cunoate poziia unui element din list atunci pe baza aceastei poziii se poate identifica
elementul din list, poziia elementului predecesor i poziia elementului succesor n list
(dac acestea exist). Ca urmare, ntr-o list se poate stabili o ordine ntre poziiile
elementelor n cadrul listei.
Poziia unui element n cadrul listei poate fi vzut n diferite moduri:
1. ca fiind dat de rangul (numrul de ordine al) elementului n cadrul listei. n acest
caz este o similitudine cu tablourile, poziia unui element n list fiind indexul
acestuia n cadrul listei. ntr-o astfel de abordare, lista este vzut ca un tablou
dinamic n care se pot accesa/aduga/terge elemente pe orice poziie n list.
2. ca fiind dat de o referin la locaia unde se stocheaz elementul listei (ex:
pointer spre locaia unde se memoreaz elementul).
Pentru a asigura generalitatea, vom abstractiza noiunea de poziie a unui element n
list i vom presupune c elementele listei sunt accesate prin intermediul unei poziii
generice.

Vom spune c o poziie p ntr-o list este valid dac este poziia unui element al
listei. Spre exemplu, dac p ar fi un pointer spre locaia unde se memoreaz un element al
listei, atunci p este valid dac este diferit de pointerul nul sau de orice alt adres care nu
reprezint adresa de memorare a unui element al listei. n cazul n care p ar fi rangul (numrul
de ordine al) elementului n list, atunci p este valid dac nu depete numrul de elemente
din list.
Ca urmare, dac ne gndim la o list liniar n care operaiile de
acces/inserare/tergere s se fac pe baza unei poziii generice n list, se ajunge la urmtorul
tip abstract de date.
Lista vid o vom nota n ceea ce urmeaz cu .
Tipul Abstract de Date LISTA
domeniu
L={l | l este o list cu elemente de tip TElement}
operaii (interfaa minimal)
creeaz(l)
descriere: se creeaz o list vid
pre: adevrat
33

post: l
L , l =
adaugSfarsit (l, e)
descriere: se adaug un element la sfritul listei
pre: l
L, e
TElement
L, l este l n care a fost adugat e la sfrit
post: l
adaugInceput(l, e)
descriere: se adaug un element la nceputul listei
pre: l
L, e
TElement
L, l este l n care a fost adugat e la nceput
post: l

valid(l, p)
descriere: funcie care verific dac o poziie n list este valid
pre: lL, p e o poziie n l
post: valid= adevrat
dac p este o poziie valid n l
fals
n caz contrar
adaugnainte(l, p, e)
descriere: se adaug un element naintea unei anumite poziii n list
pre: l
L, e
TElement, p e o poziie n l, valid(l, p)
L, l este l n care a fost inserat e nainte de poziia p
post: l
adaugDup(l, p, e)
descriere: se adaug un element dup o anumit poziie n list
L, e
TElement, p e o poziie n l, valid(l, p)
pre: l

post: l
L, l este l n care a fost inserat e dup poziia p
terge (l, p, e)
descriere: se terge elementul din list situat pe o anumit poziie
pre: l
L, e
TElement, p e o poziie n l, valid(l, p)
TElement, l
L, l este l din care a fost ters elementul de pe poziia
post: e
p, e este elementul ters
element (l, p, e)
descriere: accesarea elementului din list de pe o anumit poziie
pre: l
L, e
TElement, p e o poziie n l, valid(l, p)
TElement, e este elementul de pe poziia p din l
post: e
modifica (l, p, e)
descriere: modificarea elementului din list de pe o anumit poziie

pre: l
L, e
TElement, p e o poziie n l, valid(l, p)
post: l
L, l este l n care s-a nlocuit elementul de pe poziia p cu e
prim(l)
descriere: funcie care returneaz poziia primului element n list
L
pre: l
post: prim= poziia primului element din l sau o poziie care nu e valid
dac l e vid
ultim(l)
descriere: funcie care returneaz poziia ultimului element n list
34

L
pre: l
post: ultim= poziia ultimului element din l sau o poziie care nu e valid
dac l e vid
urmtor(l, p)
descriere: funcie care returneaz poziia n list urmtoare unei poziii
date
pre: l
L, p e o poziie n l, valid(l, p)
post: urmator= poziia din l care urmeaz poziiei p sau o poziie care nu e
valid dac p e poziia ultimului element din list
precedent(l, p)
descriere: funcie care returneaz poziia n list precedent unei poziii
date
pre: l
L, p e o poziie n l, valid(l, p)
post: precedent= poziia din l care precede poziia p sau o poziie care nu
e valid dac p e poziia primului element din list
caut(l, e)
descriere: funcie care caut un element n list
pre: l
L, e
TElement
post: caut = prima poziie pe care apare e n l sau o poziie care nu e
valid dac e l
apare(l, e)
descriere: funcie care verific apartenena unui element n list
pre: l
L, e
TElement
post: apare = adevrat e l
fals
contrar
vid(l)
descriere: funcie care verific dac lista este vid
L
pre: l
post: vid =

adevrat
fals

n cazul n care l e lista vid


n caz contrar

dim(l)
descriere: funcie care returneaz numrul de elemente din list
pre: l
L
post: dim=numrul de elemente din list
iterator(l, i)
descriere: se construiete un iterator pe list
L
pre: l
post: i este un iterator pe lista l
distruge(l)
descriere: distruge o list
L
pre: l
post: lista l a fost distrus

Reamintim modul n care va putea fi tiprit o list (ca orice alt container care poate fi
iterat) folosind iteratorul construit pe baza operaiei iterator din interfaa listei.
35

Subalgoritmul tiprire(l) este:


{pre: l este o list}
{post: se tipresc elementele listei}
iterator(l, i)
{se obine un iterator pe lista l}
Cttimp valid(i) execut
{ct timp iteratorul e valid}
element(i, e) {e este elementul curent referit de iterator}
@ tiprete e
{se tiprete elementul curent}
urmtor(i)
{iteratorul refer urmtorul element}
sfct
sf-tiprire

Observaie
Menionm faptul c nu este o modalitate unanim acceptat pentru specificarea
operaiilor. Spre exemplu, pentru operaia adaugSfarsit din interfaa TAD Lista, o alt
modalitate corect de specificare ar fi una dintre cele de mai jos:
adaugSfarsit (l, e)
desc.: se adaug un element la sfritul listei
pre: l
L, e
TElement
L, l = l {e}, e este pe ultima poziie n l
post: l

adaugSfarsit (l, e)
descriere: se adaug un element la sfritul listei
pre: l
L, e
TElement
L, l este modificat prin adugarea lui e la sfrit i pstrarea celorlate
post: l
elemente pe poziiile lor

1.5.2.

Dicionare

Dicionarele reprezint containere coninnd elemente sunt forma unor perechi (cheie,
valoare). Dicionarele pstreaz elemente n aa fel nct ele s poat fi uor localizate
folosind chei. Operaiile de baz pe dicionare sunt cutare, adugare i tergere elemente.
ntr-un dicionar cheile sunt unice i n general, o cheie are o unic valoare asociat.

Aplicaii ale dicionarelor sunt multiple. Spre exemplu:


Informaii despre conturi bancare: fiecare cont este un obiect identificat printr-un
numr de cont (considerat cheia elementului) i informaii adiionale (numele i
adresa deintorului contului, informaii despre depozite, etc). Informaiile adiionale
vor fi considerate ca fiind valoarea elementului.
Informaii despre abonai telefonici: fiecare abonat este un obiect identificat printr-un
numr de telefon (considerat cheia elementului) i informaii adiionale (numele i
adresa abonatului, informaii auxiliare, etc). Informaiile adiionale vor fi considerate
ca fiind valoarea elementului.
Informaii despre studeni: fiecare student este un obiect identificat printr-un numr
matricol (considerat cheia elementului) i informaii adiionale (numele i adresa
36

studentului, informaii auxiliare, etc). Informaiile adiionale vor fi considerate ca


fiind valoarea elementului.
Dm n continuare specificaia Tipului Abstract de Date Dicionar.
Tipul Abstract de Date DICIONAR
domeniu

D={d | d

este un dicionar cu elemente e = (c, v), c de tip TCheie, v de tip TValoare}

operaii (interfaa minimal)


creeaz(d)
descriere: se creeaz un dicionar vid
pre: true
post:d
D, d este dicionarul vid (fr elemente)
adaug(d, c, v)
descriere: se adaug un element n dicionar
pre: d
D, c
TCheie, v
TValoare
D, d=d{c, v} (se adaug n dicionar perechea (c, v))
post: d
caut(d, c)
descriere: se adaug un element n dicionar (dup cheie)
pre: d
D, c
TCheie
post: caut= v
TValoare
dac (c,v)d
elementul nul al TValoare
n caz contrar
terge(d, c)
descriere: se adaug un element n dicionar (dup cheie)
pre: d
D, c
TCheie
TValoare
dac (c,v)d, d este d din care a fost ters
post: terge= v
perechea (c,v)
elementul nul al TValoare
n caz contrar
dim(d)
descriere: funcie care returneaz numrul de elemente din list
D
pre: d
post: dim= dimensiunea dicionarului d (numrul de elemente) N*
vid(d)
descriere: funcie care verific dac dicionarul este vid
D
pre: d
post: vid= adevrat
n cazul n care d e dicionarul vid
fals
n caz contrar
chei(d, m)
descriere: se determin mulimea cheilor din dicionar
D
pre: d

post: m
M, m este mulimea cheilor din dicionarul d
valori(d, c)
37

descriere: se determin colecia valorilor din dicionar


pre: d
D
post: c
Col,
ol c este colecia valorilor din dicionarul d
perechi(d, m)
descriere: se determin mulimea perechilor (cheie, valoare) din dicionar
D
pre: d
post: m
M, m este mulimea perechilor (cheie, valoare) din dicionarul d
iterator(d, i)
descriere: se creeaz un iterator pe dicionar
pre: d
D
I, i este iterator pe dicionarul d
post:i
distruge(d)
descriere: distruge un dicionar
pre: d
D
post: dicionarul d a fost distrus

Reamintim modul n care va putea fi tiprit un dicionar (ca orice alt container care
poate fi iterat) folosind iteratorul construit pe baza operaiei iterator din interfaa
dicionarului.
Subalgoritmul tiprire(d) este:
{pre: d este un dicionar}
{post: se tipresc elementele dicionarului}
iterator(d, i)
{se obine un iterator pe dicionarul d}
Cttimp valid(i) execut
{ct timp iteratorul e valid}
element(i, e) {e este elementul curent referit de iterator}
@ tiprete e
{se tiprete elementul curent}
urmtor(i)
{iteratorul refer urmtorul element}
sfct
sf-tiprire

1.6. Probleme propuse


1. Scriei un program ntr-unul din limbajele de programare Python, C++, Java, C# care:
a. Definete o clas B avnd un atribut b de tip ntreg i o metod de tiprire care
afieaz atributul b la ieirea standard.
b. Definete o clas D derivat din B avnd un atribut d de tip ir de caractere i de
asemenea o metod de tiprire pe ieirea standard care va afia atributul b din
clasa de baz i atributul d.
c. Definete o funcie care construiete o list coninnd: un obiect o1 de tip B avnd
b egal cu 8; un obiect o2 de tip D avnd b egal cu 5 i d egal cu D5; un obiect o3
de tip B avnd b egal cu -3; un obiect o4 de tip D avnd b egal cu 9 i d egal cu
D9.
d. Definete o funcie care primete o list cu obiecte de tip B i returneaz o list
doar cu obiectele care satisfac proprietatea: b>6.
e. Pentru tipul de dat list utilizat n program, scriei specificaiile operaiilor
folosite.
38

Se pot folosi biblioteci existente pentru structuri de date (Python, C++, Java, C#). Nu se
cere implementare pentru operaiile listei.

2. Scriei un program ntr-unul din limbajele de programare Python, C++, Java, C# care:
a. Definete o clas B avnd un atribut b de tip ntreg i o metod de tiprire care
afieaz atributul b la ieirea standard.
b. Definete o clas D derivat din B avnd un atribut d de tip ir de caractere i de
asemenea o metod de tiprire pe ieirea standard care va afia atributul b din
clasa de baz i atributul d.
c. Definete o funcie care construiete un dicionar coninnd: un obiect o1 de tip B
avnd b egal cu 8; un obiect o2 de tip D avnd b egal cu 5 i d egal cu D5; un
obiect o3 de tip B avnd b egal cu -3; un obiect o4 de tip D avnd b egal cu 9 i d
egal cu D9. (cheia unui obiect din dicionar este valoarea b, iar valoarea
asociat cheii este obiectul).
d. Definete o funcie care primete un dicionar cu obiecte de tip B i verific dac
n dicionar exist un obiect care satisface proprietatea: b>6.
e. Pentru tipul de dat dicionar utilizat n program, scriei specificaiile operaiilor
folosite.
Se pot folosi biblioteci existente pentru structuri de date (Python, C++, Java, C#). Nu se
cere implementare pentru operaiile dicionarului.

39

2. Baze de date
2.1. Baze de date relaionale. Primele trei forme normale ale unei
relaii
2.1.1.

Modelul relaional

Modelul relaional de organizare a bazelor de date a fost introdus de E.F.Codd n 1970


i este cel mai studiat i mai mult folosit model de organizare a bazelor de date. In continuare
se va face o scurt prezentare a acestui model.
Fie A1, A2, ..., An o mulime de atribute (coloane, constituani, nume de date, etc.) i
D i = Dom(A i ) {?} domeniul valorilor posibile pentru atributul Ai, unde prin ? s-a notat
valoarea de nedefinit (null). Valoarea de nedefinit se folosete pentru a verifica dac unui
atribut i s-a atribuit o valoare sau el nu are valoare (sau are valoarea nedefinit). Aceast
valoare nu are un anumit tip de dat, se pot compara cu aceast valoare atribute de diferite
tipuri (numerice, iruri de caractere, date calendaristice, etc.).
Plecnd de la mulimile astfel introduse, se poate defini o relaie de gradul n sub
forma urmtoare:
R D1 D2 ... Dn ,

i poate fi considerat ca o mulime de vectori cu cte n valori, cte o valoare pentru fiecare
din atributele Ai. O astfel de relaie se poate memora printr-un tabel de forma:
R

A1

... Ai

..

An

r1

a11

... a1j

... a1n

...

...

... ...

... ...

ri

ai1

... aij

... ain

...

...

... ...

... ...

rm am1 ... amj ... amn

unde liniile din acest tabel formeaz elementele relaiei, sau tupluri, sau nregistrri, care
n general sunt distincte, i aij D j , j = 1,..., n, i = 1,..., m. Deoarece modul n care se
evideniaz elementele relaiei R de mai sus seamn cu un tabel, relaia se mai numete i
tabel. Pentru a pune n eviden numele relaiei (tabelului) i lista atributelor vom nota
aceast relaie cu:
R[A1 , A2 ,..., An ].

Modelul relaional al unei baze de date const dintr-o colecie de relaii ce variaz n
timp (coninutul relaiilor se poate schimba prin operaii de adugare, tergere i actualizare).
O baz de date relaional const din trei pri:
1. Datele (relaii sau tabele, legturi ntre tabele) i descrierea acestora;

40

2. Reguli de integritate (pentru a memora numai valori corecte n relaii);


3. Operatori de gestiune a datelor.

Exemplul 1. STUDENTI [NUME, ANUL_NASTERII, ANUL_DE_STUDIU],


cu urmtoarele valori posibile:

NUME
ANUL_NASTERII ANUL_DE_STUDIU
Pop Ioan
1985
2
Barbu Ana
1987
1
Dan Radu
1986
3
Exemplul 2. CARTE [AUTORI, TITLU, EDITURA, AN_APARITIE],
cu valorile:

AUTORI
Date, C.J.

TITLU
EDITURA
AN_APARITIE
An Introduction to Database
Addison-Wesley
2004
Systems
Publishing Comp.
2011
Ullman, J.,
A First Course in Database Systems Addison-Wesley +
Prentice-Hall
Widom, J.
Helman, P.
The Science of Database
Irwin, SUA
1994
Management
Ramakrishnan, R. Database Management
McGraw-Hill
2007
Systems
Pentru fiecare relaie se poate preciza un atribut sau o colecie de atribute, din cadrul
relaiei, numit cheie, cu rol de identificare a elementelor relaiei (cheia ia valori diferite
pentru nregistrri diferite din relaie, deci fiecare nregistrare se poate identifica prin
valoarea cheii). Dac se d cte o valoare pentru atributele din cheie, se poate determina linia
(una singur) n care apar aceste valori. Vom presupune c nici o submulime de atribute din
cheie nu este cheie. Deoarece toate elementele relaiei sunt diferite, o cheie exist totdeauna
(n cel mai ru caz cheia este format din toate atributele relaiei). Pentru exemplul 1 se poate
alege NUME ca i cheie (atunci n baza de date nu pot exista doi studeni cu acelai nume),
iar pentru exemplul 2 se poate alege grupul de atribute {AUTORI, TITLU, EDITURA,
AN_APARITIE} ca i cheie, sau s se introduc un nou atribut (de exemplu COTA) pentru
identificare.
Pentru anumite relaii pot fi alese mai multe chei. Una dintre chei (un atribut simplu sau
un atribut compus din mai multe atribute simple) se alege cheie principal (primar), iar
celelalte se vor considera chei secundare. Sistemele de gestiune a bazelor de date nu permit
existena a dou elemente distincte ntr-o relaie cu aceeai valoare pentru oricare cheie
(principal sau secundar), deci precizarea unei chei constituie o restricie pentru baza de
date.

Exemplul 3. ORAR [ZI, ORA, SALA, PROFESOR, CLASA, DISCIPLINA],


cu orarul pe o sptmn. Se pot alege ca i chei urmtoarele mulimi de atribute:
{ZI, ORA, SALA}; {ZI, ORA, PROFESOR}; {ZI, ORA, CLASA}.
Valorile unor atribute dintr-o relaie pot s apar n alt relaie. Plecnd de la o relaie
R2 se pot cuta nregistrrile dintr-o relaie R1 dup valorile unui astfel de atribut (simplu
sau compus). In relaia R2 se stabilete un atribut A, numit cheie extern. Valorile

41

atributului A se caut printre valorile cheii din relaia R1. Cele dou relaii R1 i R2 nu este
obligatoriu s fie distincte.

R1

A=cheie extern
R2

chei

Exemplu:
CLASE [cod, profil]
ELEVI [nrmatricol, nume, clasa, datanasterii].

Legtura o putem stabili ntre relaia CLASE (considerat ca printe pentru legtur) i
relaia ELEVI (ca membru pentru legtur) prin egalitatea CLASE.cod=ELEVI.clasa. Unei
anumite clase (memorat n relaia CLASE), identificat printr-un cod, i corespund toi
elevii din clasa cu codul respectiv.
Prin cheie extern se pot memora legturi 1:n ntre entiti: la o clas corespund
orici elevi, iar unui elev i este asociat cel mult o clas.
Cheia extern se poate folosi i pentru a memora legturi m:n ntre entiti.
Fie dou entiti: discipline i studeni. La o disciplin sunt "inscrii" mai muli studeni, iar
un student are asociate mai multe discipline. Varianta de memorare cuprinde o relaie
intermediar.

Pentru ca valorile dintr-o baz de date s fie corecte, la definirea bazei de date se pot
preciza anumite restricii de intergritate (ele sunt verificate de sistemul de gestiune a bazei
de date la modificarea datelor din tabele). Aceste restricii se refer la o coloan, la un tabel,
la o legtur ntre dou tabele:

42

restricii asociate coloanei:


o Not Null - coloana nu poate s primeasc valori nedefinite
o Primary Key - coloana curent se definete cheia primar
o Unique - valorile coloanei sunt unice

o Check(condiie) - se d condiia pe care trebuie s o ndeplineasc valorile


coloanei (condiii simple, care au valoarea true sau false)
o Foreign Key REFERENCES tabel_parinte [(nume_coloana)] [On Update actiune]
[On Delete actiune] - coloana curent este cheie extern
restricii asociate tabelului:
o Primary key(lista coloane) - definirea cheii primare pentru tabel
o Unique(lista coloane) - valorile sunt unice pentru lista de coloane precizat
o Check(condiie) - pentru a preciza condiia pe care trebuie s o ndeplineasc
valorile unei linii
o Foreign Key nume_cheie_externa(lista_coloane) REFERENCES tabel_parinte
[(lista_coloane)] [On Update actiune] [On Delete actiune] - se definete cheia
extern

2.1.2.

Primele trei forme normale ale unei relaii

In general anumite date se pot reprezenta n mai multe moduri prin relaii (la modelul
relaional). Pentru ca aceste date s se poat prelucra ct mai simplu (la o operaie de
actualizare a datelor s nu fie necesare teste suplimentare) este necesar ca relaiile n care se
memoreaz datele s verifice anumite condiii (s aib un anumit nivel de normalizare).
Pn n prezent se cunosc mai multe forme normale pentru relaii, dintre care cele mai
cunoscute sunt: 1NF, 2NF, 3NF, BCNF, 4NF, 5NF. Avem urmtoarele incluziuni pentru
relaii n diferite forme normale:

1NF
2NF
3NF
BCNF
4NF
5NF
Dac o relaie nu este de o anumit form normal, atunci ea se poate descompune n
mai multe relaii de aceast form normal.

Definiie. Pentru descompunerea unei relaii se folosete operatorul de proiecie. Fie


R[ A1 , A2 ,..., An ] o relaie i = Ai1 , Ai2 ,..., Ai p
o submulime de atribute,

A1 , A2 ,..., An . Prin proiecia relaiei R pe se nelege relaia:

R' Ai1 , Ai2 ,..., Ai p = ( R ) ={A


unde:

i1 , Ai2

,..., Ai p

}( R ), ,

r = (a1 , a 2 ,..., a n ) R (r ) = r [ ] = ai1 , ai 2 ,..., ai p R ' ,


i toate elementele din R' sunt distincte.

43

Definiie. Pentru compunerea relaiilor se folosete operatorul de join natural. Fie


R[ , ] , S [ , ] dou relaii peste mulimile de atribute , , , = . Prin joinul
natural al relaiilor R i S se nelege relaia:

R S [ , , ] =

{( (r), (r ), (s)) r R, s S

si

(r ) = (s)}.

O relaie R se poate descompune n mai multe relaii noi R1 , R2 ,..., Rm . Aceast


descompunere este bun dac R = R1 R2 ... Rm , deci datele din R se pot obine din
datele memorate n relaiile R1 , R2 ,..., Rm i nu apar date noi prin aceste operaii de
compunere.

Exemplu de descompunere care nu este bun: fie relaia:


ContracteStudiu[Student ,CadruDidactic, Disciplina],
i dou noi relaii obinute prin proiecia acestei relaii: SC[Student, CadruDidactic] i
CD[CadruDidactic, Disciplina]. Presupunem c pentru relaia iniial avem urmtoarele
valori:

R Student CadruDidactic Disciplina


r1
s1
c1
d1
r2
s2
c2
d2
r3
s1
c2
d3
Folosind definiia proieciei se obin urmtoarele valori pentru cele dou relaii obinute
din R i pentru joinul natural al acestor relaii:

SC
r1
r2
r3

Student
s1
s2
s1

CadruDidactic
c1
c2
c2

CD
r1
r2
r3

CadruDidactic
c1
c2
c2

Disciplina
d1
d2
d3

SC*CD Student CadruDidactic Disciplina


r1
s1
c1
d1
r2
s2
c2
d2
?
s2
c2
d3
?
s1
c2
d2
s1
c2
d3
r3
Se observ c n relaia SC*CD se obin nregistrri suplimentare fa de relaia iniial, deci
descompunerea sugerat nu este bun.

Observaie. Prin atribut simplu vom nelege un atribut oarecare din relaie, iar prin
atribut compus vom nelege o mulime de atribute (cel puin dou) din relaie.

44

Este posibil ca n diverse aplicaii practice s apar atribute (simple sau compuse) ce
iau mai multe valori pentru un element din relaie. Aceste atribute formeaz un atribut
repetitiv.

Exemplul 4. Fie relaia:


STUDENT [NUME, ANULNASTERII, GRUPA, DISCIPLINA, NOTA],
cu atributul NUME ca i cheie. In acest exemplu perechea {DISCIPLINA, NOTA} este un
grup repetitiv. Putem avea urmtoarele valori n aceast relaie:

NUME
Pop Ioan

ANULNASTERII GRUPA DISCIPLINA


1998
221
Baze de date
Sisteme de operare
Probabiliti
Murean Ana 1999
222
Baze de date
Sisteme de operare
Probabiliti
Proiect individual

NOTA
10
9
8
8
7
10
9

Exemplul 5. Fie relaia:


CARTE [Cota, NumeAutori, Titlu, Editura, AnApariie, Limba, CuvinteCheie],
cu atributul Cota ca i cheie i atributele repetitive NumeAutori i CuvinteCheie. Atributul
Cota poate avea o semnificaie efectiv (s existe o cot asociat la fiecare carte) sau s fie
introdus pentru existena cheii (valorile s fie distincte, eventual pot s fie generate automat).

Grupurile repetitive creaz foarte multe greuti n memorarea diverselor relaii i din
aceast cauz se ncearc evitarea lor, fr ns a pierde date. Dac R[A] este o relaie, unde
A este mulimea atributelor, iar formeaz un grup repetitiv (atribut simplu sau compus),
atunci R se poate descompune n dou relaii fr ca s fie atribut repetitiv. Dac C este o
cheie pentru relaia R, atunci cele dou relaii n care se descompune relaia R sunt:
R' [C ] = C (R ) i R' ' [A ] = A (R ) .
Exemplul 6. Relaia STUDENT din exemplul 4 se descompune n urmtoarele dou
relaii:
DATE_GENERALE [NUME, ANULNASTERII, GRUPA],
REZULTATE [NUME, DISCIPLINA, NOTA].
Exemplul 7. Relaia CARTE din exemplul 5 se descompune n urmtoarele trei relaii
(n relaia CARTE exist dou grupuri repetitive):
CARTI [Cota, Titlu, Editura, AnApariie, Limba],
AUTORI [Cota, NumeAutor],
CUVINTE_CHEIE [Cota, CuvntCheie].
Observaie. Dac o carte nu are autori sau cuvinte cheie asociate, atunci ea va avea
cte o nregistrare n relaiile AUTORI sau CUVINTE_CHEIE n care al doilea atribut are
valoarea null. Dac se dorete eliminarea acestor nregistrri, atunci relaia CARTE nu se va
putea obine din cele trei relaii numai prin join natural (sunt necesari operatori de join
extern).
Definiie. O relaie este de prima form normal (1NF) dac ea nu conine grupuri
(de atribute) repetitive.

45

Sistemele de gestiune a bazelor de date relaionale permit descrierea numai a relaiilor


ce se afl n 1NF. Exist i sisteme ce permit gestiunea relaiilor non-1NF (exemplu Oracle,
unde o coloan poate fi un obiect sau o colecie de date, sau mai recent bazele de date
NoSQL).
Urmtoarele forme normale ale unei relaii utilizeaz o noiune foarte important, i
anume dependena funcional dintre diverse submulimi de atribute. Stabilirea
dependenelor funcionale este o sarcin a administratorului bazei de date i depinde de
semnificaia (semantica) datelor ce se memoreaz n relaie. Operaiile de actualizare a
datelor din relaie (nserare, tergere, modificare) nu trebuie s modifice dependenele
funcionale (dac pentru relaie exist astfel de dependene).

Definiie. Fie R[ A1 , A2 ,..., An ] o relaie i , {A1 , A2 ,..., An } dou submulimi de


atribute. Atributul (simplu sau compus) este dependent funcional de atributul (simplu
sau compus), notaie: , dac i numai dac fiecare valoare a lui din R are asociat
o valoare precis i unic pentru (aceast asociere este valabil tot timpul existenei
relaiei R). O valoare oarecare a lui poate s apar n mai multe linii ale lui R i atunci
fiecare dintre aceste linii conine aceeai valoare pentru atributul , deci:

(r ) = (r ') implic (r ) = (r ') .


Valoarea din implicaia (dependena) se numete determinant, iar este
determinat.

Observaie. Dependena funcional se poate folosi ca o proprietate (restricie) pe care


baza de date trebuie s o ndeplineasc pe perioada existenei acesteia: se adaug, elimin,
modific elemente n relaie numai dac dependena funcional este verificat.
Existena unei dependene funcionale ntr-o relaie nseamn c anumite asocieri de
valori se memoreaz de mai multe ori, deci apare o redundan. Pentru exemplificarea unor
probleme care apar vom lua relaia urmtoare, care memoreaz rezultatele la examene pentru
studeni:
Exemplul 8. EXAMEN [NumeStudent, Disciplina, Nota, CadruDidactic],
unde cheia este {NumeStudent, Disciplina}. Deoarece unei discipline i corespunde un singur
cadru didactic, iar unui cadru didactic pot s-i corespund mai multe discipline, putem cere ca
s fie ndeplinit restricia (dependena) {Disciplina} {CadruDidactic}.

Examen
1
2
3
4
5

NumeStudent
Alb Ana
Costin Constantin
Alb Ana
Enisei Elena
Frian Florin

Disciplina
Matematic
Istorie
Istorie
Matematic
Matematic

Nota
10
9
8
9
10

CadruDidactic
Rus Teodor
Popa Horea
Popa Horea
Rus Teodor
Rus Teodor

Dac pstrm o astfel de dependen funcional, atunci pot apare urmtoarele probleme:

Risip de spaiu: aceleai asocieri se memoreaz de mai multe ori. Legtura dintre
disciplina de Matematic i profesorul Rus Teodor este memorat de trei ori, iar dintre
disciplina Istorie i profesorul Popa Horea se memoreaz de dou ori.

Anomalii la actualizare: schimbarea unei date ce apare ntr-o asociere implic efectuarea
acestei modificri n toate asocierile (fr a se ti cte astfel de asocieri exist), altfel baza

46

de date va conine erori (va fi inconsistent). Dac la prima nregistrare se schimb


valoarea atributului CadruDidactic i nu se face aceeai modificare i la nregistrrile 4 i
5, atunci modificarea va introduce o eroare n relaie.

Anomalii la nserare: la adugarea unei nregistrri trebuie s se cunoasc valorile


atributelor, nu se pot folosi valori nedefinite pentru atributele implicate n dependenele
funcionale.

Anomalii la tergere: la tergerea unor nregistrri se pot terge i asocieri (ntre valori)
ce nu se pot reface. De exemplu, dac se terg nregistrrile 2 i 3, atunci asocierea dintre
Disciplina i CadruDidactic se pierde.

Anomaliile de mai sus apar datorit existenei unei dependene funcionale ntre
mulimi de atribute. Pentru a elimina situaiile amintite trebuie ca aceste dependene
(asocieri) de valori s se pstreze ntr-o relaie separat. Pentru aceasta este necesar ca relaia
iniial s se descompun, fr ca prin descompunere s se piard date sau s se introduc
date noi prin compunerea de relaii (trebuie ca descompunerea "s fie bun"). O astfel de
descompunere se face n momentul proiectrii bazei de date, cnd se pot stabili dependenele
funcionale.

Observaii. Se pot demonstra uor urmtoarele proprieti simple pentru dependenele


funcionale:
1. Dac C este o cheie pentru R[ A1 , A2 ,..., An ] , atunci C , {A1 , A2 ,..., An }.
2. Dac , atunci , numit dependena funcional trivial sau reflexivitatea.
( r1 ) = ( r2 ) ( r1 ) = ( r2 )

3. Dac , atunci , cu .
( r1 ) = ( r2 ) ( r1 ) = ( r2 ) ( r1 ) = ( r2 )

4. Dac i , atunci , care este proprietatea de tranzitivitate a


dependenei funcionale.
( r1 ) = ( r2 ) ( r1 ) = ( r2 ) ( r1 ) = ( r2 )

5. Dac i A , atunci , unde = .


( r1 ) = ( r2 ) ( r1 ) = ( r2 )
( r1 ) = ( r2 )
( r1 ) = ( r2 )
( r1 ) = ( r2 )

Definiie. Un atribut A (simplu sau compus) se numete prim dac exist o cheie C i
A C (C este o cheie compus, sau A este chiar o cheie a relaiei). Dac un atribut nu este
inclus n nici o cheie, atunci se numete neprim.
Definiie. Fie R[A1 , A2 ,..., An ] i , {A1 , A2 ,..., An } . Atributul este complet
dependent funcional de dac este dependent funcional de (deci ) i nu
este dependent funcional de nici o submulime de atribute din ( , nu este
adevrat).
Observaie. Dac atributul nu este complet dependent funcional de (deci
este dependent de o submulime a lui ), atunci este un atribut compus.

Definiie. O relaie este de a doua form norml (2NF) dac:


este de prima form normal,

47

orice atribut neprim (simplu sau compus) (deci care nu este inclus ntr-o cheie) este
complet dependent funcional de oricare cheie a relaiei.

Observaie. Dac o relaie este de prima form normal (1NF) i nu este de a doua
form normal (2NF), atunci are o cheie compus (dac o relaie nu este de a doua form
normal, atunci exist o dependen funcional cu atribut inclus ntr-o cheie).
Pentru a preciza modul de descompunere pentru cazul general, fie R[A1, A2 ,..., An ] o
relaie i C A = {A1 , A2 ,..., An } o cheie. Presupunem c exist A , C = ( este
un atribut necheie), dependent funcional de C ( este complet dependent
funcional de o submulime strict de atribute din cheie). Dependena se poate
elimina dac relaia R se descompune n urmtoarele dou relaii:

R' [ ] = [ ] (R ) i R' ' [ A ] = A (R ) .


Vom analiza relaia din exemplul 8:

EXAMEN [NumeStudent, Disciplina, Nota, CadruDidactic],


unde cheia este {NumeStudent, Disciplina} i exist dependena funcional (restricia)
{Disciplina} {CadruDidactic}. De aici deducem c atributul CadruDidactic nu este
complet dependent funcional de o cheie, deci relaia EXAMEN nu este de a doua form
normal. Eliminarea acestei dependene funcionale se poate face prin descompunerea relaiei
n urmtoarele relaii:

APRECIERI [NumeStudent, Disciplina, Nota];


STAT_FUNCTII [Disciplina, CadruDidactic].
Exemplul 9. Presupunem c pentru memorarea contractelor de studiu se folosete
relaia:
CONTRACTE[Nume, Prenume, CNP, CodDisciplina, DenumireDisciplina].
Cheia relaiei este {CNP,CodDisciplina}. In relaie mai exist dou dependene funcionale:

{CNP} {Nume, Prenume} i {CodDisciplina} {DenumireDisciplina}. Pentru eliminarea


acestor dependene se descompune relaia n urmtoarele:
STUDENTI [CNP, Nume, Prenume],
INDRUMATORI [CodDisciplina, DenumireDisciplina],
CONTRACTE [CNP, CodDisciplina].
Pentru a treia form normal este necesar noiunea de dependen tranzitiv.

Definiie. Un atribut Z este tranzitiv dependent de atributul X dac Y nct X Y,


Y Z, iar Y X nu are loc i Z nu este inclus n X Y .
Definiie. O relaie este de a treia form normal (3NF) dac este 2NF i orice
atribut neprim nu este tranzitiv dependent de oricare cheie a relaiei.
Dac C este o cheie i un atribut tranzitiv dependent de cheie, atunci exist un
atribut care verific: C (dependen care este verificat totdeauna) i .
Deoarece relaia este 2NF, obinem c este complet dependent de C, deci C . De aici
deducem c o relaie ce este 2NF i nu este 3NF are o dependen , iar este atribut
neprim. Aceast dependen se poate elimina prin descompunerea relaiei R n mod
asemntor ca la eliminarea dependenelor de la 2NF.

48

Exemplul 10. Rezultatele obinute de absolveni la lucrarea de licen sunt trecute n


relaia:
LUCRARI_LICENTA [NumeAbsolvent, Nota, CadruDidIndr, Departament].
Aici se memoreaz numele cadrului didactic ndrumtor i denumirea departamentului la care
se afl acesta. Deoarece se introduc date despre absolveni, cte o nregistrare pentru un
absolvent, putem s stabilim c NumeAbsolvent este cheia relaiei. Din semnificaia
atributelor incluse n relaie se observ urmtoarea dependen funcional:

{CadruDidIndr} {Departament}.
Din existena acestei dependene funcionale se deduce c relaia nu este de 3NF.
Pentru a elimina dependena funcional, relaia se poate descompune n urmtoarele dou
relaii:

REZULTATE [NumeAbsolvent, Nota, CadruDidIndr]


INDRUMATORI [CadruDidIndr, Departament].
Exemplul 11. Presupunem c adresele unui grup de persoane se memoreaz n urmtoarea
relaie:
ADRESE [CNP, Nume, Prenume, CodPostal, LocalitateDomiciliu, Strada, Nr].
Cheia relaiei este {CNP}. Deoarece la unele localiti codul potal se stabilete la nivel de
strad, sau chiar poiuni de strad, exist dependena funcional:

{CodPostal} {LocalitateDomiciliu}.
Deoarece exist aceast dependen funcional, deducem c relaia ADRESE nu este de a
treia form normal, deci este necesar descompunerea ei.

Exemplul 12. S considerm urmtoarea relaie care memoreaz o eventual planificare a


studenilor pentru examene:
PLANIFICARE_EX [Data, Ora, Cadru_did, Sala, Grupa],
cu urmtoarele restricii (cerine care trebuie respectate) i care se transpun n definirea de
chei sau de dependene funcionale:
1. Un student d maximum un examen ntr-o zi, deci {Grupa, Data} este cheie.
2. Un cadru didactic are examen cu o singur grup la o anumit or, deci {Cadru_did, Data,
Ora} este cheie.
3. La un moment dat ntr-o sal este planificat cel mult un examen, deci {Sala, Data, Ora}
este cheie.
4. Intr-o zi cadrul didactic nu schimb sala, n sala respectiv pot fi planificate i alte
examene, dar la alte ore, deci exist urmtoarea dependen funcional:
{Cadru_did, Data} {Sala}
Toate atributele din aceast relaie apar n cel puin o cheie, deci nu exist atribute
neprime. Avnd n vedere definiia formelor normale precizate pn acuma, putem spune c
relaia este n 3NF. Pentru a elimina i dependenele funcionale de tipul celor pe care le
avem n exemplul de mai sus s-a introdus o nou form normal:

Definiie. O relaie este n 3NF Boyce-Codd, sau BCNF, dac orice determinant
(pentru o dependen funcional) este cheie, deci nu exist dependene funcionale
astfel nct s nu fie cheie.
49

Pentru a elimina dependena funcional amintit mai sus trebuie s facem urmtoarea
descompunere pentru relaia PLANIFICARE_EX:

PLANIFICARE_EX [Data, Cadru_did, Ora, Student],


REPARTIZARE_SALI [Cadru_did, Data, Sala].
Dup aceast descompunere nu mai exist dependene funcionale, deci relaiile sunt de
tipul BCNF, dar a disprut cheia asociat restriciei precizate la punctul 3 de mai sus: {Sala,
Data, Ora}. Dac se mai dorete pstrat o astfel de restricie, atunci ea trebuie verificat
altfel (de exemplu, prin program).

2.2. Interogarea BD cu operatori din algebra relaional


Pentru a explica limbajul de interogare (cererea de date) bazat pe algebra relaiilor vom
preciza la nceput tipurile de condiii ce pot apare n cadrul diferiilor operatori relaionali.
1. Pentru a verifica dac un atribut ndeplinete o condiie simpl se face compararea acestuia
cu o anumit valoare, sub forma:
nume atribut operator_relaional valoare
2. O relaie cu o singur coloan poate fi considerat ca o mulime. Urmtoarea condiie
testeaz dac o anumit valoare aparine sau nu unei mulimi:
IS IN
nume_atribut
relaie_cu_o_coloan
IS NOT IN

3. Dou relaii (considerate ca mulimi de nregistrri) se pot compara prin operaiile de


egalitate, diferit, incluziune, neincluziune. Intre dou relaii cu acelai numr de
coloane i cu aceleai tipuri de date pentru coloane (deci ntre dou mulimi
comparabile) putem avea condiii de tipul urmtor:
IS IN
IS NOT IN

relaie
relaie
=

<>

4. Tot condiie este i oricare din construciile urmtoare:

(condiie)
NOT condiie
condiie1 AND condiie2
condiie1 OR condiie2
unde condiie, condiie1, condiie2 sunt condiii de tipurile 1-4.
In primul tip de condiie apare construcia 'valoare', care poate fi una din tipurile
urmtoare. Pentru fiecare construcie se ia n valoare o anumit relaie curent, care rezult
din contextul n care apare aceasta.

50

nume_atribut - care precizeaz valoarea atributului dintr-o nregistrare curent. Dac


precizarea numai a numelui atributului creaz ambiguitate (exist mai multe relaii
curente care conin cte un atribut cu acest nume), atunci se va face o calificare a
atributului cu numele relaiei sub forma: relaie.atribut.

expresie - dac se evalueaz expresia, iar dac apar i denumiri de atribute, atunci acestea
se iau dintr-o nregistrare curent.

COUNT(*) FROM relaie - precizeaz numrul de nregistrri din relaia specificat.

COUNT
SUM

AVG ([DISTINCT ] nume _ atribut ) - care determin o valoare plecnd de la toate


MAX

MIN
nregistrrile din relaia curent. La determinarea acestei valori se iau toate valorile
atributului precizat ca argument (din toate nregistrrile), sau numai valorile distincte,
dup cum lipsete sau apare cuvntul DISTINCT. Valorile astfel determinate sunt:
numrul de valori (pentru COUNT), suma acestor valori (apare SUM, valorile trebuie s
fie numerice), valoarea medie (apare AVG, valorile trebuie s fie numerice), valoarea
maxim (apare MAX), respectiv valoarea minim (apare MIN).

In continuare se vor preciza operatorii care se pot folosi pentru interogarea bazelor
de date relaionale.

Selecia (sau proiecia orizontal) a unei relaii R - determin o nou relaie ce are aceeai
schem cu a relaiei R. Din relaia R se iau numai nregistrrile care ndeplinesc o
condiie c. Notaia pentru acest operator este: c ( R ) .

Proiecia (sau proiecia vertical) - determin o relaie nou ce are atributele precizate
printr-o mulime de atribute. Din fiecare nregistrare a unei relaii R se determin numai
valorile atributelor incluse n mulimea . Mulimea de atribute se poate extinde la o
mulime de expresii (n loc de o mulime de atribute), care precizeaz coloanele relaiei
care se construiete. Notaia pentru acest operator este: ( R) .

Produsul cartezian a dou relaii: R1R2 - care determin o relaie nou ce are ca
atribute concatenarea atributelor din cele dou relaii, iar fiecare nregistrare din R1 se
concateneaz cu fiecare nregistrare din R2.

Reuniunea, diferena i intersecia a dou relaii: R1 R2, R1 - R2, R1 R2. Cele dou
relaii trebuie s aib aceeai schem.

Exist mai muli operatori join.

Joinul condiional sau theta join, notat prin R1 R2 - care determin acele
nregistrri din produsul cartezian al celor dou relaii care ndeplinesc o anumit
condiie. Din definiie se observ c avem: R1 R2= ( R1 R2 ) .
Joinul natural, notat prin R1 R2, care determin o relaie nou ce are ca atribute
reuniunea atributelor din cele dou relaii, iar nregistrrile se obin din toate perechile
de nregistrri ale celor dou relaii care au aceleai valori pentru atributele comune.
Dac cele dou relaii au schemele R1 [ ], R2 [ ] , i = {A1 , A2 ,..., An }, atunci
joinul natural se poate calcula prin construcia urmtoare:
R1 R2 =

R1
R2

R . A = R . A and ... and R . A = R . A


1 1
2 1
1 n
2 n

51

Joinul extern stnga, notat (n acest material) prin R1 > C R2 , determin o relaie nou
ce are ca atribute concatenarea atributelor din cele dou relaii, iar nregistrrile se obin
astfel: se iau nregistrrile care se obin prin joinul condiional R1 c R2, la care se
adaug nregistrrile din R1 care nu s-au folosit la acest join condiional combinate cu
valoarea null pentru toate atributele corespunztoare relaiei R2.
Joinul extern dreapta, notat prin R1 < C R2 , se obine asemntor ca joinul extern
stnga, dar la nregistrrile din R1 c R2 se adaug nregistrrile din R2 care nu s-au
folosit la acest join condiional combinate cu valoarea null pentru toate atributele
corespunztoare relaiei R1.

Ctul pleac de la dou relaii R1 [ ], R 2[ ] ,

R1

cu , i se noteaz prin R1 R2 [ ].
Deducem c atributele din ct sunt date de
mulimea . O nregistrare r R1 R2
dac r2 R2 , r1 R1 ce ndeplinete
condiiile:
1.
2.

r1

r2

r
r2

(r1 ) = r ;

r2

R2

(r1 ) = r2 .

Semnificaia relaiei ct se vede i din figura


alturat. O nregistrare r1 aparine ctului dac
n relaia R1 apar toate concatenrile dintre
aceast nregistrare i fiecare nregistrare din R2.
O problem important legat de operatorii descrii mai sus const n determinarea unei
submulimi independente de operatori. O mulime M de operatori este independent dac
eliminnd un operator oarecare op din M se diminueaz puterea mulimii, adic va exista o
relaie obinut cu operatori din M i care nu se poate obine cu operatori din mulimea
M - {op}.
Pentru limbajul de interogare descris mai sus, o mulime independent este format din
submulimea: { , , , , }. Ceilali operatori se obin dup regulile urmtoare (unele
expresii au fost deja deduse mai sus):

R1 R2 = R1 ( R1 R2 ) ;

R1 c R2= c ( R1 R2 ) ;

R1 [ ], R 2[ ] , i = {A1 , A2 ,..., An }, atunci


R1 R2 =

;
R1
R
2

R . A = R . A and ... and R . A = R . A


1 1
2 1
1 n
2 n

Fie R1 [ ], R 2[ ] , i R3 [ ] = (null, ... , null), R4 [ ] = (null, ... , null).


R1 > C R2 = (R1 c R2) [R1 -

52

(R

C R 2 ) ] R3 .

R1 < C R2 = (R1 c R2) R4 [R2

(R

C R2 ) ].

Dac R1 [ ], R 2[ ] , cu , atunci rR1 R2 dac r2 R1 R2 , r1 R1 ce


ndeplinete condiiile:

(r1 ) = r

De aici deducem c r este din


ce au o parte n

(r1 ) = r2 .

( R1 ) . In ( R1 ) R2 sunt toate elementele

( R1 ) i a doua parte n R2. Din relaia astfel obinut vom elimina

pe R1 i rmn acele elemente ce au o parte n (R1 ) i nu au cealalt parte n

( R1 ) . De aici obinem:
R1 R2 = ( R1 )

((

( R1 ) R2 R1 .

La lista de operatori relaionali amintii mai sus se pot aminti cteva instruciuni utile la
rezolvarea unor probleme:

Atribuirea: unei variabile (relaii) R i vom atribui o relaie dat printr-o expresie
construit cu operatorii de mai sus. In instruciune se poate preciza, pentru R, i
denumirea coloanelor.
R[lista] := expresie

Eliminarea duplicrilor unei relaii: (R )

Sortarea nregistrrilor dintr-o relaie: s{lista} (R)

Gruparea: {lista1} group by {lista 2} ( R ) , care este o extensie pentru proiecie. Inregistrrile din R
sunt grupate dup coloanele din lista2, iar pentru un grup de nregistrri cu aceleai valori
pentru lista2 se evalueaz lista1 (unde pot apare funcii de grupare).

2.3. Interogarea bazelor de date relaionale cu SQL


Pentru gestiunea bazelor de date relaionale s-a construit limbajul SOL (Structured
Query Language), ce permite gestiunea componentelor unei baze de date (tabel, index,
utilizator, procedur memorat, etc.).
Scurt istoric:
1970 - E.F. Codd formalizeaz modelul relaional
1974 - la IBM (din San Jose) se definete limbajul SEQUEL (Structured English
Query Language)
1975 - se definete limbajul SQUARE (Specifying Queries as Relational
Expressions).
1976 - la IBM se definete o versiune modificat a limbajului SEQUEL, cu numele
SEQUEL/2. Dup o revizuire devine SQL
1986 - SQL devine standard ANSI (American National Standards Institute)
1987 - SQL este adoptat de ISO (International Standards Organization)
1989 - se public extensia SQL89 sau SQL1
1992 - se face o revizuire i se obine SQL2 sau SQL92

53

1999 - se complecteaz SQL cu posibiliti de gestiune orientate obiect, rezultnd


SQL3 (sau SQL1999)
2003 - se adaug noi tipuri de date noi funcii, rezultnd SQL2003.

Comanda SELECT este folosit pentru interogarea bazelor de date (obinerea de


informaii). Aceast comand este cea mai complex din cadrul sistemelor ce conin comenzi
SQL. Comanda permite obinerea de date din mai multe surse de date. Ea are, printre altele,
funciile de selectie, proiectie, produs cartezian, join i reuniune, intersecie i diferen din
limbajul de interogare a bazelor de date relaionale bazat pe algebra relaiilor. Sintaxa
comenzii este dat n continuare.

ALL

SELECT DISTINCT


exp [AS cmp ] [exp [AS cmp ]]...
TOP n [PERCENT]
FROM sursa1 [alias] [,sursa2 [alias]]...
[WHERE condiie]
[GROUP BY lista_cmpuri [HAVING condiie]]
UNION [ALL]

[ INTERSECT comanda_SE LECT


EXCEPT

cmp ASC
cmp ASC
ORDER BY


, ORDER BY
...
nrcmp DESC
nrcmp DESC

Aceast comand selecteaz date din sursele de date precizate n clauza FROM. Pentru
precizarea (calificarea) cmpurilor (dac este necesar, deci dac folosirea numai a numelui
cmpului produce ambiguitate, adic exist mai multe cmpuri cu acest nume n sursele de
date) se poate folosi numele tabelului sau un nume sinonim (alias local numai n comanda
SELECT) stabilit n FROM dup numele sursei de date. Dac se definete un "alias", atunci
calificarea se face numai cu el (nu se va mai face cu numele tabelului).
O construcie numit sursa poate fi:
1. un tabel sau view din baza de date
2. (instruciune_select)
3. expresie_join, sub forma:
sursa1 [alias] operator_join sursa2 [alias] ON condiie_legatur
(expresie_join)

O condiie_elementar de legtur dintre dou surse de date (precizate prin expresie_tabel)


este de forma:
[alias_sursa1.]cmp1 operator [alias_sursa2.]cmp2
unde operator poate fi: =, <>, >, >=, <, <=. Cei doi termeni ai comparatiei trebuie s aparin
la tabele diferite.

Condiiile de legtur dintre dou surse de date sunt de forma:

cond_elementara [AND cond_elementara] ...

(condiie)

54

O expresie join are ca rezultat un tabel i este de forma:

INNER

LEFT [OUTER]
Sursa1
JOIN Sursa2 ON conditie
RIGHT [OUTER]

FULL [OUTER]
Joinul condiional, din algebra relaional, notat prin Sursa1 c Sursa2, este precizat
prin Sursa1 INNER JOIN sursa2 ON condiie, i determin acele nregistrri din produsul
cartezian al celor dou surse care ndeplinesc condiia din ON.
Joinul extern stnga, precizat prin Sursa1 LEFT [OUTER] JOIN sursa2 ON
condiie, determin o surs nou ce are ca atribute concatenarea atributelor din cele dou
surse, iar nregistrrile se obin astfel: se iau nregistrrile care se obin prin joinul condiional
Sursa1 c Sursa2, la care se adaug nregistrrile din sursa1 care nu s-au folosit la acest join
condiional combinate cu valoarea null pentru toate atributele corespunztoare din Sursa2.
Joinul extern dreapta, precizat prin Sursa1 RIGHT [OUTER] JOIN sursa2 ON
condiie, determin o surs nou ce are ca atribute concatenarea atributelor din cele dou
surse, iar nregistrrile se obin astfel: se iau nregistrrile care se obin prin joinul condiional
Sursa1 c Sursa2, la care se adaug nregistrrile din sursa2 care nu s-au folosit la acest join
condiional combinate cu valoarea null pentru toate atributele corespunztoare din Sursa1.
Joinul extern total, precizat prin Sursa1 FULL [OUTER] JOIN sursa2 ON condiie,
se obine prin reuniunea rezultatelor obinute de joinul extern stnga i joinul extern dreapta.
Alte tipuri de expresii join:

Sursa1 JOIN Sursa2 USING (lista_coloane)

Sursa1 NATURAL JOIN Sursa2

Sursa1 CROSS JOIN Sursa2

Dac n clauza FROM apar mai multe surse de date (care se vor evalua la un tabel),
atunci ntre un astfel de tabel - pe care l vom numi tabel principal, i celelalte tabele este
indicat s existe anumite legturi (stabilite prin condiii). Plecnd de la fiecare nregistrare a
tabelului principal se determin nregistrrile din celelalte tabele asociate prin astfel de
legturi (deci nregistrrile ce verific o condiie). Dac legtura (condiia) nu se stabilete,
atunci se consider c ea asociaz toate nregistrrile din celelalte tabele pentru fiecare
nregistrare a tabelului principal (se consider c valoarea condiiei este true atunci cnd ea
lipsete). Aceast condiie de legtur dintre sursele de date se precizeaz prin:

FROM sursa1[, sursa2] ... WHERE condiie_legtur


Folosind sursele de date din FROM i eventuala condiie de legtur (dac exist mai
multe surse de date) va rezulta un tabel_rezultat, cu coloanele obinute prin concatenarea
coloanelor din sursele de date, iar nregistrrile sunt determinate dup cum sunt explicate mai
sus.
In tabel_rezultat se pot pstra toate nregistrrile obinute din sursele de date, sau se
poate face o filtrare prin utilizarea unei condiii de filtrare. Aceasta condiie de filtrare va fi
trecut n clauza WHERE n continuarea condiiei de legtur. Cu o condiie de filtrare
condiia din WHERE este de forma:

WHERE condiie_filtrare
55

WHERE condiie_legtur AND condiie_filtrare


Condiia de filtrare din clauza WHERE poate fi construit dup urmtoarele reguli.

Condiiile elementare de filtrare pot fi de una din formele urmtoare:

expresie operator_relational expresie

expresie [NOT] BETWEEN valmin AND valmax


pentru a verifica dac valoarea unei expresii este cuprins ntre dou valori (valmin i
valmax) sau nu este cuprins ntre aceste valori (apare NOT)

cmp (NOT) LIKE ablon


Dup LIKE apare un ablon (ca un ir de caractere) ce precizeaz o mulime de valori.
In funcie de sistemul de gestiune folosit, exist un caracter n ablon ce precizeaz locul
unui singur caracter necunoscut n cmp, sau un caracter n ablon ce precizeaz un ir
neprecizat de caractere n cmp.

valoare [valoare] ...


expresie [NOT] IN

(subselectie )
Se verific dac valoarea expresiei apare (sau nu - cu NOT) ntr-o list de valori sau ntro subselecie. O subselecie este o surs de date generat cu comanda SELECT i care
are numai un singur cmp - cu valori de acelai tip cu valorile expresiei. Aceast condiie
corespunde testului de "apartenen" al unui element la o mulime.

ALL

cmp operator_relational ANY (subselectie)


SOME

Valorile cmpului din stnga operatorului relaional i valorile singurului cmp din
subselecie trebuie s fie de acelai tip. Se obine valoarea adevrat pentru condiie dac
valoarea din partea stng este n relaia dat de operator pentru:

o toate valorile din subselectie (apare ALL),


o cel puin o valoare din subselectie (apare ANY sau SOME).
Condiii echivalente:

"expresie IN (subselecie)" echivalent cu "expresie = ANY (subselecie)"


"expresie NOT IN (subselecie)" echivalent cu "expresie <> ALL (subselecie)"

[NOT] EXISTS (subselectie)


Cu EXISTS se obtine valoarea adevrat dac n subselecie exist cel puin o
nregistrare, i fals dac subselecia este vid. Prezena lui NOT inverseaz valorile de
adevr.

56

O condiie de filtrare poate fi:


o condiie elementar
o (condiie)
o not condiie
o condiie1 and condiie2
o condiie1 or condiie2

O condiie elementar poate avea una din valorile: true, false, null. Valoarea null se
obine dac unul din operanzii utilizai are valoarea null. Valorile de adevr pentru operatorii
not, and, or sunt date n continuare:
true false null
false true null

not
and
true
false
null

true
true
false
null

false
false
false
false

null
null
false
null

or
true
false
null

true
true
true
true

false
true
false
null

null
true
null

Din aceast succesiune de valori se pot selecta toate cmpurile din toate tabelele (apare
"*" dup numele comenzii), sau se pot construi cmpuri ce au ca valoare rezultatul unor
expresii. Cmpurile cu aceeai denumire n tabele diferite se pot califica prin numele sau
alias-ul tabelelor surs. Numele cmpului sau expresiei din tabelul rezultat este stabilit
automat de sistem (n functie de expresia ce-l genereaz), sau se poate preciza prin clauza AS
ce urmeaz expresiei (sau cmpului). In acest fel putem construi valori pentru o nou
nregistrare ntr-un tabel_final.
Expresiile se precizeaz cu ajutorul operanzilor (cmpuri, rezultatul unor funcii) i a
operatorilor corespunztori tipurilor de operanzi.
In tabelul final se pot include toate sau numai o parte din nregistrri, dup cum e
precizat printr-un predicat ce apare n faa listei de coloane::
ALL - toate nregistrrile
DISTINCT - numai nregistrrile distinte
TOP n - primele n nregistrri
TOP n PERCENT - primele n% nregistrri
Inregistrrile din "tabelul final" se pot ordona cresctor (ASC) sau descresctor
(DESC) dup valorile unor cmpuri, precizate n clauza ORDER BY. Cmpurile se pot
preciza prin nume sau prin poziia lor (numrul cmpului) n lista de cmpuri din comanda
SELECT (precizarea prin poziie este obligatorie atunci cnd se dorete sortarea dup
valorile unei expresii). Ordinea cmpurilor din aceast clauz precizeaz prioritatea cheilor
de sortare.
Mai multe nregistrri consecutive din "tabelul final* pot fi grupate ntr-o singur
nregistrare, deci un grup de nregistrri se nlocuiete cu o singur nregistrare. Un astfel de
grup este precizat de valorile comune ale cmpurilor ce apar n clauza GROUP BY.
"Tabelul nou" se sorteaz (automat de ctre sistem) creasctor dup valorile cmpurilor din
GROUP BY. Inregistrrile consecutive din fiierul astfel sortat, ce au aceeai valoare pentru
toate cmpurile din GROUP BY, se nlocuiesc cu o singur nregistrare. Prezena unei astfel
de nregistrri poate fi condiionat de valoarea adevrat pentru o condiie ce se trece n
clauza HAVING.
Pentru grupul de nregistrri astfel precizat (deci pentru o mulime de valori) se pot
folosi urmtoarele funcii:

ALL
cmp sau AVG([ALL]) expresie)
AVG

DISTINCT

Pentru grupul de nregistrri se iau toate valorile (cu ALL, care este i valoarea
implicit) sau numai valorile distincte (apare DISTINCT) ale cmpului sau expresiei
57

numerice precizate i din aceste valori se determin valoarea medie.

ALL

COUNT

cmp
DISTINCT

Aceast funcie determin numrul de nregistrri din grup (apare '*'), numrul de valori
ale unui cmp (apare ALL, identic cu '*'), sau numrul de nregistrri distincte din grup
(cu DISTINCT).

ALL
cmp sau SUM([ALL]) expresie)
SUM

DISTINCT

Pentru nregistrrile din grup se face suma valorilor unui cmp sau ale unei expresii
numerice (deci numrul de termeni este dat de numrul de nregistrri din grup) sau suma
valorilor distincte ale cmpului.

MAX
MAX ALL
([ALL]) expresie)


cmp sau
MIN
MIN DISTINCT

Pentru fiecare nregistrare din grup se determin valoarea unei expresii sau cmp i se
afl valoarea maxim sau minim dintre aceste valori.
Cele cinci funcii amintite mai sus (AVG, COUNT, SUM, MIN, MAX) pot apare att
n expresiile ce descriu cmpurile din fiierul rezultat, ct i n clauza HAVING. Deoarece
aceste funcii se aplic unui grup de nregistrri, n comada SELECT acest grup trebuie
generat de clauza GROUP BY. Dac aceast clauz lipsete, atunci ntregul "tabel final"
constituie un grup, deci tabelul rezultat va avea o singur nregistrare.
In general nu este posibil selectarea cmpurilor singure (fr rezultat al funcilor
amintite) dect numai dac au fost trecute n GROUP BY. Dac totui apar, i aceast
folosire nu produce eroare, atunci se ia o valoare oarecare, pentru o nregistrare din grup.
Dou tabele cu acelai numr de cmpuri (coloane) i cu acelai tip pentru valorile
cmpurilor aflate pe aceleai poziii se pot reuni ntr-un singur tabel obinut cu ajutorul
operatorului UNION. Din tabelul rezultat obinut se pot pstra toate nregistrrile (apare
ALL) sau numai cele distincte (nu apare ALL). Clauza ORDER BY poate apare numai
pentru ultima selecie. Nu se pot combina subselecii prin clauza UNION.
Intre dou rezultate (mulimi de nregistrri) obinute cu instruciuni SELECT se poate
folosi operatorul INTERSEC sau EXCEPT (sau MINUS).
Clauzele din instruciunea SELECT trebuie s fie n ordinea: lista_expresii FROM ...
WHERE ... HAVING ... ORDER BY ...
O comand se poate memora n baza de date ca o component numit view. Definirea
este:
CREATE VIEW nume_view AS comanda_SELECT

58

2.4. Probleme propuse


I.
a. Se cere o baz de date relaional, cu tabele n 3NF, ce gestioneaz urmtoarele
informaii dintr-o firm de soft:
activiti: cod activitate, descriere, tip activitate;
angajai: cod angajat, nume, list activiti, echipa din care face parte, liderul
echipei;
unde:
o activitate este identificat prin "cod activitate";
un angajat este identificat prin "cod angajat";
un angajat face parte dintr-o singur echip, iar echipa are un lider, care la rndul
su este angajat al firmei;
un angajat poate s participe la realizarea mai multor activiti, iar la o activitate
pot s participe mai muli angajai;
Justificai c tabelele obinute sunt n 3NF.
b. Pentru baza de date de la punctul a, s se rezolve, folosind algebra relaional sau
Select-SQL, urmtoarele interogri:
b1. Numele angajailor care lucreaz la cel puin o activitate de tipul "Proiectare" i
nu lucreaz la nici o activitate de tipul "Testare".
b2. Numele angajailor care sunt liderii unei echipe cu cel puin 10 angajai.
II.
a. Se cere o baz de date relaional, cu tabele n 3NF, ce gestioneaz urmtoarele
informaii dintr-o facultate:
discipline: cod, denumire, numr credite, lista studenilor care au dat examen;
studenti: cod, nume, data naterii, grupa, anul de studiu, specializarea, lista
disciplinelor la care a dat examene (inclusiv data examenului i nota obinut).
Justificai c tabelele sunt n 3NF.
b. Pentru baza de date de la punctul a, folosind algebra relaional i instructiuni
SELECT-SQL, se cer studenii (nume, grupa, nr.discipline promovate) ce au promovat
n anul 2013 peste 5 discipline. Dac un student are la o disciplin mai multe examene
cu note de promovare, atunci disciplina se numr o singur dat.
III.
a. Se cere o baz de date relaional, cu tabele n 3NF, ce gestioneaz urmtoarele
informaii despre absolvenii nscrii pentru examnul de licen: Nr.matricol, Cod i
denumire secie absolvit, Titlul lucrrii, Cod i nume cadru didactic ndrumtor, Cod
i denumire departament de care aparine cadrul didactic ndrumtor, Lista de resurse
soft necesare pentru susinerea lucrrii (ex. C#.Net, C++, etc.), Lista de resurse hard
necesare pentru susinerea lucrrii (Ram 8Gb, DVD Reader, etc.). Justificai c tabelele
sunt n 3NF.
b. Pentru baza de date de la punctul a, folosind algebra relaional i instructiuni
SELECT-SQL cel puin o dat fiecare, se cer urmtoarele liste (explicai modul de
obinere a listelor):
i. Absolvenii (nume, titlu lucrare, nume cadru didactic) pentru care cadrul
didactic ndrumtor aparine de un departament dat prin denumire.
ii. Pentru departament se cere numrul de absolveni care au cadrul didactic
ndrumtor de la acest departament.
iii. Cadrele didactice care nu au ndrumat absolveni la lucrarea de licen.
iv. Numele absolvenilor care au nevoie de urmatoarele dou resurse soft: Oracle i
C#.

59

3. Structura i funciile sistemelor de operare.


Sisteme de fiiere
3.1. Sistemul de Fiiere Unix
3.1.1.

3.1.1.1.

Structura Intern a Discului UNIX

Partiii i Blocuri

Un sistem de fiiere Unix este gzduit fie pe un periferic oarecare (hard-disc, CD, dischet
etc.), fie pe o partiie a unui hard-disc. Partiionarea unui hard-disc este o operaie
(relativ) independent de sistemul de operare ce va fi gzduit n partiia respectiv. De
aceea, att partiilor, ct i suporturilor fizice reale le vom spune generic, discuri Unix.

Structura unui disc UNIX

Un fiier Unix este o succesiune de octei, fiecare octet putnd fi adresat n mod individual.
Este permis att accesul secvenial, ct i cel direct. Unitatea de schimb dintre disc i
memorie este blocul. La sistemele mai vechi acesta are 512 octei, iar la cele mai noi pn la
4Ko, pentru o mai eficient gestiune a spaiului. Un sistem de fiiere Unix este o structur de
date rezident pe disc. Aa dup cum se vede din figura de mai sus, un disc este compus din
patru categorii de blocuri.
Blocul 0 conine programul de ncrcare al SO. Acest program este dependent de maina sub
care se lucreaz.
Blocul 1 este numit i superbloc. In el sunt trecute o serie de informaii prin care se
definete sistemul de fiiere de pe disc. Printre aceste informaii amintim:
-numrul n de inoduri (detaliem imediat);
-numrul de zone definite pe disc;
-pointeri spre harta de bii a alocrii inodurilor;
-pointeri spre harta de bii a spaiului liber disc;
-dimensiunile zonelor disc etc.
Blocurile 2 la n, unde n este o constant a formatrii discului. Un inod (sau i-nod) este
numele, n terminologia Unix, a descriptorului unui fiier. Inodurile sunt memorate pe
60

disc sub forma unei liste (numiti-list). Numrul de ordine al unui inod n cadrul i-listei se
reprezint pe doi octei i se numete i-numr. Acest i-numr constituie legtura dintre fiier
i programele utilizator.
Partea cea mai mare a discului este rezervat zonei fiierelor. Alocarea spaiului pentru fiiere
se face printr-o variant elegant de indexare. Informaiile de plecare pentru alocare sunt
fixate n inoduri.

3.1.1.2.

Directori i I-noduri

Structura unei intrri ntr-un fiier director este ilustrat n figura de mai jos

Structura unei intrri n director

Deci, n director se afl numele fiierului i referina spre inodul descriptor al fiierului.
Un inod are, de regul, ntre 64 i 128 de octei i el conine informaiile din tabelul urmtor:

3.1.1.3.

Schema de alocare a blocurilor disc pentru un fiier

Fiecare sistem de fiiere Unix are cteva constante proprii, printre care amintim:
lungimea unui bloc, lungimea unui inod, lungimea unei adrese disc, cte adrese de prime
blocuri se nregistreaz direct n inod i cte referine se trec n lista de referine indirecte.
Indiferent de valorile acestor constante, principiile de nregistrare / regsire sunt aceleai.
Pentru fixarea ideilor, vom alege aceste constante cu valorile ntlnite mai frecvent la
sistemele de fiiere deja consacrate. Astfel, vom presupune c un bloc are lungimea de 512
octei. O adres disc se reprezint pe 4 octei, deci ntr-un bloc se pot nregistra 128 astfel de
se adrese. In inod trec direct primele 10 adrese de blocuri, iar lista de adrese indirecte are
3 elemente. Cu aceste constante, n figura de mai jos este prezentat structura pointerilor
spre blocurile ataate unui fiier Unix.

61

Structura unui inod i accesul la blocurile unui fiier

In inodul fiierului se afl o list cu 13 intrri, care desemneaz blocurile fizice aparinnd
fiierului.
Primele 10 intrri conin adresele primelor 10 blocuri de cte 512 octei care aparin
fiierului.
Intrarea nr. 11 conine adresa unui bloc, numit bloc de indirectare simpl. El conine
adresele urmtoarelor 128 blocuri de cte 512 octei, care aparin fiierului.
Intrarea nr. 12 conine adresa unui bloc, numit bloc de indirectare dubl. El conine adresele
a 128 blocuri de indirectare simpl, care la rndul lor conin, fiecare, adresele a cte 128
blocuri, de 512 octei fiecare, cu informaii aparinnd fiierului.
Intrarea nr. 13 conine adresa unui bloc, numit bloc de indirectare tripl. In acest
bloc sunt coninute adresele a 128 blocuri de indirectare dubl, fiecare dintre acestea
coninnd adresele a cte 128 blocuri de indirectare simpl, iar fiecare dintre acestea
conine adresele a cte 128 blocuri, de cte 512 octei, cu informaii ale fiierului.
In figura de mai sus am ilustrat prin cercuri blocurile de informaie care aparin
fiierului, iar prin dreptunghiuri blocurile de referine, n interiorul acestora marcnd
referinele. Numrul de accese necesare pentru a obine direct un octet oarecare este cel mult
4. Pentru fiiere mici acest numr este i mai mic. Att timp ct fiierul este deschis,
inodul lui lui este prezent n memoria intern. Tabelul urmtor d numrul maxim de accese
la disc pentru a obine, n acces direct orice octet dintr-un fiier, n funcie de lungimea
fiierului.

62

La sistemele Unix actuale lungimea unui bloc este de 4096 octei care poate nregistra 1024
adrese, iar n inod se nregistreaz direct adresele primelor 12 blocuri. In aceste
condiii, tabelul de mai sus se transform n:

3.1.2.

Tipuri de fiiere i sisteme de fiiere

In cadrul unui sistem de fiiere, apelurile sistem Unix gestioneaz opt tipuri de fiiere i
anume:
1. Normale (obinuite)
2. Directori
3. Legturi hard (hard links)
4. Legturi simbolice (symbolic links)
5. Socketuri (sockets)
6. FIFO - pipe cu nume (named pipes)
7. Periferice caracter
8. Periferice bloc
Pe lng aceste opt tipuri, mai exist nc patru entiti, pe care apelurile sistem le vd, din
punct de vedere sintactic, tot ca i fiiere. Aceste entiti sunt gestionate de nucleul Unix, au
suportul fizic tot n nucleu i folosite la comunicri ntre procese. Aceste entiti sunt:
9. Pipe (anonymous pipes)
10. Segmente de memorie partajat
11. Cozi de mesaje
12. Semafoare
Fiierele obinuite sunt privite ca iruri de octei, accesul la un octet putndu-se face fie
secvenial, fie direct prin numrul de ordine al octetului.

63

Fiierele directori. Un fiier director se deosebete de un fiier obinuit numai prin informaia
coninut n el. Un director conine lista de nume i adrese pentru fiierele subordonate lui.
Uzual, fiecare utilizator are un director propriu care puncteaz la fiierele lui obinuite, sau la
ali subdirectori definii de el.
Fiierele speciale. In aceast categorie putem include, pentru moment, ultimele 6 tipuri de
fiiere. In particular, Unix privete fiecare dispozitiv de I/O ca i un fiier de tip special. Din
punct de vedere al utilizatorului, nu exist nici o deosebire ntre lucrul cu un fiier disc
normal i lucrul cu un fiier special.
Fiecare director are dou intrri cu nume speciale i anume:
"." (punct) denumete generic (puncteaz spre) nsui directorul respectiv;
".." (dou puncte succesive), denumete generic (puncteaz spre) directorul printe.
Fiecare sistem de fiiere conine un director principal numit root sau /.
In mod obinuit, fiecare utilizator folosete un director curent, ataat utilizatorului la intrarea
n sistem. Utilizatorul poate s-i schimbe acest director (cd), poate crea un nou director
subordonat celui curent, (mkdir), s tearg un director (rmdir), s afieze calea de acces
de la root la un director sau fiier (pwd) etc.
Apariia unui mare numr de distribuitori de Unix a condus, inevitabil, la proliferarea unui
numr oarecare de "sisteme de fiiere extinse" proprii acestor distribuitori. De exemplu:
Solaris utilizeaz sistemul de fiiere ufs;
Linux utilizeaz cu precdere sistemul de fiiere ext2 i mai nou, ext3;
IRIX utilizeaz xfs
etc.
Actualele distribuii de Unix permit utilizatea unor sisteme de fiiere proprii altor sisteme de
operare. Printre cele mai importante amintim:
Sistemele FAT i FAT32 de sub MS-DOS i Windows 9x;
Sistemul NTFS propriu Windows NT i 2000.
Din fericire, aceste extinderi sunt transparente pentru utilizatorii obinuii. Totui, se
recomand pruden atunci cnd se efectueaz altfel de operaii dect citirea din fiierele
create sub alte sisteme de operare dect sistemul curent. De exemplu, modificarea sub Unix a
unui octet ntr-un fiier de tip doc creat cu Word sub Windows poate uor s compromit
fiierul aa nct el s nu mai poat fi exploatat sub Windows!
Administratorii sistemelor Unix trebuie s in cont de sistemele de fiiere pe care le
instaleaz i de drepturile pe care le confer acestora vis-a-vis de userii obinuii.
Principiul structurii arborescente de fiiere este acela c orice fiier sau director are un singur
printe. Automat, pentru fiecare director sau fiier exist o singur cale (path) de la rdcin
la directorul curent. Legtura ntre un director sau fiier i printe o vom numi legtur
natural. Evident ea se creeaz odat cu crearea directorului sau fiierului respectiv.

64

3.1.2.1.

Legturi hard i legturi simbolice

In anumite situaii este util partajarea unei poriuni a structurii de fiiere ntre mai muli
utilizatori. De exemplu, o baz de date dintr-o parte a structurii de fiiere trebuie s fie
accesibil mai multor utilizatori. Unix permite o astfel de partajare prin intermediul
legturilor suplimentare. O legtur suplimentar permite referirea la un fiier pe alte ci
dect pe cea natural. Legturile suplimentare sunt de dou feluri: legturi hard i legturi
simbolice (soft).
Legturile hard sunt identice cu legturile naturale i ele pot fi create numai de ctre
administratorul sistemului. O astfel de legtur este o intrare ntr-un director care puncteaz
spre o substructur din sistemul de fiiere spre care puncteaz deja legtura lui natural. Prin
aceasta, substructura este vzut ca fiind descendent din dou directoare diferite! Deci,
printr-o astfel de legtur un fiier primete efectiv dou nume. Din aceast cauz, la
parcurgerea unei structuri arborescente, fiierele punctate prin legturi hard apar duplicate.
Fiecare duplicat apare cu numrul de legturi ctre el.
De exemplu, dac exist un fiier cu numele vechi, iar administratorul d comanda:

#ln

vechi

linknou

atunci n sistemul de fiiere se vor vedea dou fiiere identice: vechi si linknou, fiecare
dintre ele avnd marcat faptul c sunt dou legturi spre el.
Legturile hard pot fi fcute numai n interiorul aceluiai sistem de fiiere (detalii puin mai
trziu).
Legturile simbolice sunt intrri speciale ntr-un director, care puncteaz (refer) un fiier
(sau director) oarecare n structura de directori. Aceast intrare se comport ca i un
subdirector al directorului n care s-a creat intrarea.
In forma cea mai simpl, o legtur simbolic se creeaz prin comanda:

ln

- s

caleInStructuraDeDirectori

numeSimbolic

Dup aceast comand, caleInStructuraDeDirectori va avea marcat o legtur n


plus, iar numeSimbolic va indica (numai) ctre aceast cale. Legturile simbolice pot fi
utilizate i de ctre userii obinuii. De asemenea, ele pot puncta i nafara sistemului de
fiiere.

65

/:

A:

D:

E: F

A
..

..

A
-

B:

D:

..

C:

A
..

E:

F:

C:

F:

G:

G:

Structura arborescent mpreun cu legturile simbolice sau hard confer sistemului de fiiere
Unix o structur de graf aciclic. In exemplul din figura de mai sus este prezentat un exemplu
simplu de structur de fiiere. Prin literele mari A, B, C, D, E, F, G am indicat nume de
fiiere obinuite, nume de directori i nume de legturi. Este evident posibil ca acelai nume
s apar de mai multe ori n structura de directori, graie structurii de directori care elimin
ambiguitile. Fiierele obinuite sunt reprezentate prin cercuri, iar fiierele directori prin
dreptunghiuri.
Legturile sunt reprezentate prin sgei de trei tipuri:
linie continu legturile naturale;
linie ntrerupt spre propriul director i spre printe;
linie punctat legturi simbolice sau hard.
In exemplul de mai sus exist 12 noduri - fiiere obinuite sau directori. Privit ca un arbore,
deci considernd numai legturile naturale, el are 7 ramuri i 4 nivele.
S presupunem c cele dou legturi (desenate cu linie punctata) sunt simbolice. Pentru
comoditate, vom nota legtura simbolic cu ultima liter din specificarea cii. Crearea celor
dou legturi se poate face, de exemplu, prin succesiunea de comenzi:
cd /A
ln -s /A/B/D/G G
Prima legtur
cd /A/B/D
ln -s /A/E E
A doua legtur
S presupunem acum c directorul curent este B. Vom parcurge arborele n ordinea director
urmat de subordonaii lui de la stnga spre dreapta. Urmtoarele 12 linii indic toate cele 12
noduri din structur. Pe aceeai linie apar, atunci cnd este posibil, mai multe specificri ale
aceluiai nod. Specificrile care fac uz de legturi simbolice sunt subliniate. Cele mai lungi 7
ramuri vor fi marcate cu un simbol # n partea dreapt.

66

/
/A
/A/D
/A/E
/A/E/F
/A/E/G
/B
/B/D
/B/D/G
/B/E
/B/F
/C

..
../A
../A/D
../A/E
../A/E/F
../A/E/G
.
D
D/G
E
F
../C

#
D/E
D/E/F
D/E/G
./D
./D/G
./E
./F

./D/E
./D/E/F
./D/E/G

/A/G

#
#

../A/G

#
#
#
#

Ce se ntmpl cu tergerea n cazul legturilor multiple? De exemplu, ce se ntmpl cnd se


execut una dintre urmtoarele dou comenzi?

rm
rm

D/G
/A/G

Este clar c fiierul trebuie s rmn activ dac este ters numai de ctre una dintre
specificri.
Pentru aceasta, n descriptorul fiierului respectiv exist un cmp numit contor de legare.
Acesta are valoarea 1 la crearea fiierului i crete cu 1 la fiecare nou legtur. La tergere,
se radiaz legtura din directorul printe care a cerut tergerea, iar contorul de legare scade cu
1. Abia dac acest contor a ajuns la zero, fiierul va fi efectiv ters de pe disc i blocurile
ocupate de el vor fi eliberate.

3.2. Procese Unix


Procese Unix: creare, funciile fork, exec, exit, wait; comunicare prin pipe i FIFO

3.2.1.

Principalele apeluri system de gestiune a proceselor

In seciunile din acest subcapitol vom prezenta cele mai importante apeluri sistem pentru
lucrul cu procese: fork, exit, wait i exec*. Incepem cu fork(), apelul sistem
pentru crearea unui proces.
3.2.1.1.

Crearea proceselor Unix. Apelul fork

In sistemul de operare Unix un proces se creeaz prin apelul funciei sistem fork(). La o
funcionare normal, efectul acesteia este urmtorul: se copiaz imaginea procesului ntr-o
zon de memorie liber, aceast copie fiind noul proces creat, n prima faz identic cu
procesul iniial. Cele dou procese i continu execuia n paralel cu instruciunea care
urmeaz apelului fork.

67

Procesul nou creat poart numele de proces fiu, iar procesul care a apelat funcia fork() se
numete proces printe. Exceptnd faptul c au spaii de adrese diferite, procesul fiu difer de
procesul printe doar prin identificatorul su de proces PID, prin identificatorul procesului
printe PPID i prin valoarea returnat de apelul fork(). La derulare normal, un apel
fork() ntoarce n procesul printe (procesul care a lansat apelul fork()) pid-ul noului
proces fiu, iar n procesul fiu, ntoarce valoarea 0.
Inainte de fork:

Dupa fork:
Proces parinte

Proces parinte
Context

fork()

Context

>0 (pid fiu)

fork()

Proces fiu
0

Context

fork()

Figura 3.1 Mecanismul fork


In figura de mai sus am ilustrat acest mecanism, unde sgeile indic instruciunea care se
execut n mod curent n proces.
In caz de eec, fork ntoarce valoarea 1 i desigur, seteaz corespunztor variabila errno.
Eecul apelului fork poate s apar dac:
nu exist memorie suficient pentru efectuarea copiei imaginii procesului printe;
numrul total de procese depeste o limit maxim admis.
Acest comportament al lui fork permite descrierea uoar a dou secvene de instruciuni
care s se deruleze n paralel, sub forma:

if ( fork() == 0 )
{ - - - instruciuni ale procesului fiu - - - }
else
{ - - - instruciuni ale procesului tat - - - }
Programul urmtor ilustreaz utilizarea lui fork:
main(){
int pid,i;
printf("\nInceputul programului:\n");
if ((pid=fork())<0) err_sys("Nu pot face fork()\n");
else if (pid==0){//Suntem in fiu
for (i=1;i<=10;i++){

68

sleep(2);
//dormim 2 secunde
printf("\tFIUL(%d) al PARINTELUI(%d):3*%d=%d\n",
getpid(),getppid(),i,3*i);
}
printf("Sfarsit FIU\n");
}
else if (pid>0){//Suntem in parinte
printf("Am creat FIUL(%d)\n",pid);
for (i=1;i<=10;i++){
sleep(1);
//dormim 1 secunda
printf("PARINTELE(%d):2*%d=%d\n",getpid(),i,2*i);
}
printf("Sfarsit PARINTE\n");
}

}
In mod intenionat, am fcut astfel nct procesul fiu s atepte mai mult dect printele (n
cazul calculelor complexe apar adesea situaii n care operaiile unuia dintre procese dureaz
mai mult n timp). Ca urmare, printele va termina mai repede execuia. Rezultatele obinute
sunt:
Inceputul programului:
Am creat FIUL(20429)
PARINTELE(20428): 2*1=2
FIUL(20429) al PARINTELUI(20428): 3*1=3
PARINTELE(20428): 2*2=4
PARINTELE(20428): 2*3=6
FIUL(20429) al PARINTELUI(20428): 3*2=6
PARINTELE(20428): 2*4=8
PARINTELE(20428): 2*5=10
FIUL(20429) al PARINTELUI(20428): 3*3=9
PARINTELE(20428): 2*6=12
PARINTELE(20428): 2*7=14
FIUL(20429) al PARINTELUI(20428): 3*4=12
PARINTELE(20428): 2*8=16
PARINTELE(20428): 2*9=18
FIUL(20429) al PARINTELUI(20428): 3*5=15
PARINTELE(20428): 2*10=20
Sfarsit PARINTE
FIUL(20429) al PARINTELUI(1): 3*6=18
FIUL(20429) al PARINTELUI(1): 3*7=21
FIUL(20429) al PARINTELUI(1): 3*8=24
FIUL(20429) al PARINTELUI(1): 3*9=27
FIUL(20429) al PARINTELUI(1): 3*10=30
Sfarsit FIU

3.2.1.2.

Execuia unui program extern; apelurile exec

Aproape toate sistemele de operare i toate mediile de programare ofer, ntr-un fel sau altul,
mecanisme de lansare a unui program din interiorul altuia. Unix ofer acest mecanism prin
intermediul familiei de apeluri sistem exec*. Dup cum se va vedea, utilizarea combinat de
fork exec ofer o mare elasticitate manevrrii proceselor.
Apelurile sistem din familia exec lanseaz un nou program n cadrul aceluiai proces.
Apelului exec i se furnizeaz numele unui fiier executabil, iar coninutul acestuia se
suprapune peste programul procesului existent, aa cum se vede n fig. 5.10.
69

Inainte de exec:

Dupa exec:

Context
vechi

Context
vechi
Context
nou

exec()

ou
mn
gra
pro

Figura 3.2 Mecanismul exec


In urma lui exec instruciunile aflate n programul curent nu se mai execut, n locul lor se
lanseaz instruciunile noului program.
Unix ofer, n funcie de trei criterii, sase astfel de apeluri, ilustrate n fig. 5.11. Criteriile
sunt:
Specificarea cii spre programul executabil ce va fi lansat: absolut sau relativ la
directoarele indicate prin variabila de mediu PATH.
Mediul este motenit sau se creeaz un nou mediu.
Specificarea argumentelor din linia de comand se face printr-o list explicit sau printrun vector de pointeri spre aceste argumente.
Din cele opt posibile, s-au eliminat cele dou cu cale relativ i mediu nou. Prototipurile celor
ase apeluri exec*() sunt:
int execv (char *fisier, char *argv[]);
int execl (char *fisier, char *arg0, , char *argn,
NULL);
int execve(char *fisier, char *argv[], char *envp[]);
int execle(char *fisier, char *arg0, , char *argn, NULL,
char *envp[]);
int execvp(char *fisier, char *argv[]);
int execlp(char* fisier, char *arg0, , char *argn,
NULL);

Semnificaia parametrilor exec este urmtoarea:


fisier - numele fiierului executabil care va nlocui programul curent. El trebuie s
coincid cu argumentul argv[0] sau arg0.
argv este tabloul de pointeri, terminat cu un pointer NULL, care conine argumentele
liniei de comand pentru noul program lansat n execuie.
arg0, arg1, ... , argn, NULL conine argumentele liniei de comand
scrise explicit ca i stringuri; aceast secven trebuie terminat cu NULL.
envp este tabloul de pointeri, terminat cu un pointer NULL, care conine stringurile
corespunztoare noilor variabile de mediu sub forma nume=valoare.

70

3.2.1.3.

Apelurile exit i wait

Apelul sistem:
exit(int n)
provoac terminarea procesului curent i revenirea la procesul printe (cel care l-a creat prin
fork). Intregul n precizeaz codul de retur cu care se termin procesul. In cazul n care
procesul printe nu mai exist, procesul este trecul n starea zombie i este subordonat
automat procesului special init (care are PID-ul 1).
Ateptarea terminrii unui proces se realizeaz folosind unul dintre apelurile sistem wait()
sau waitpid(). Prototipurile acestora sunt:
pid_t wait(int *stare)
pid_t waitpid(pid_t pid, int *stare, int optiuni);

Apelul wait() suspend execuia programului pn la terminarea unui proces fiu. Dac fiul
s-a terminat nainte de apelul wait(), apelul se termin imediat. La terminare, toate
resursele ocupate de procesul fiu sunt eliberate.

3.2.2.

3.2.2.1.

Comunicarea ntre procese prin pipe

Conceptul de pipe

Conceptul a aprut prima dat sub Unix, pentru a permite unui proces fiu s comunice cu
printele su. De obicei procesul printe redirecteaz ieirea sa standard, stdout, ctre un
pipe, iar procesul fiu i redirecteaz intrarea standard, stdin, din acelai pipe. In
majoritatea sistemelor de operare se folosete operatorul | pentru specificarea acestui gen
de conexiuni ntre comenzi ale sistemului de operare.
Un pipe Unix este un flux unidirecional de date, gestionat de ctre nucleul sistemului. De
fapt, n nucleu se rezerv un buffer de minimum 4096 octei n care octeii sunt gestionai aa
cum am descris mai sus. Crearea unui pipe se face prin apelul sistem:
int pipe(int fd[2]);

Intregul fd[0] se comport ca un ntreg care identific descriptorul pentru citirea din
"fiierul" pipe, iar fd[1] ca un ntreg care indic descriptorul pentru scriere n pipe. In urma
crerii, legtura user nucleu prin acest pipe apare ca n figura de mai jos.

71

read fd[0]
proces user:
write fd[1]

pipe

nucleu:

sensul datelor

Evident, un pipe ntr-un singur proces nu are sens. Este ns esenial funcionarea lui pipe
combinat cu fork. Astfel, dac dup crearea lui pipe se execut un fork, atunci legtura
celor dou procese cu pipe din nucleu apare ca n figura urmtoare.
Procesul parinte

Procesul fiu
read

read

fd[0]

write
fd[1]

fd[0]
fd[1]

write

Nucleu
pipe
Sensul datelor
Figura 3.3 Un pipe leag dou procese nrudite
Asigurarea unidirecionalitii unui pipe cade exclusiv n sarcina programatorului. Astfel,
pentru a se asigura sensul datelor n exemplul de mai sus, se impune ca nainte de a transmite
prin pipe:
In procesul printe s se apeleze close(fd[0]);
In procesul fiu s se apeleze close(fd[1]);
Natural, dac se dorete ordinea invers, atunci se vor executa operaiile close(fd[1]) n
procesul printe i close(fd[0]) n procesul fiu.

3.2.2.2.

Exemplu: implementarea who | sort prin pipe i exec

S considerm acum comanda shell compus:

72

$ who | sort

Noi vom prezenta realizarea conexiunii ntre cele dou comenzi: who | sort prin pipe.
Procesul printe (care nlocuiete procesul shell) genereaz doi fii, iar acetia i redirecteaz
corespunztor intrrile / ieirile. Primul dintre ele execut who, cellalt sort, iar printele
ateapt terminarea lor. Sursa este prezentat n programul de mai jos.
//whoSort.c
//Lanseaza in pipe comenzile shell: $ who | sort
#include <unistd.h>
main (){
int p[2];
pipe (p);
if (fork () == 0) {
// Primul fiu
dup2 (p[1], 1);
//redirectarea iesirii standard
close (p[0]);
execlp ("who", "who", 0);
}
else if (fork () == 0) {
// Al doilea fiu
dup2 (p[0], 0);
// redirectarea intrarii standard
close (p[1]);
execlp ("sort", "sort", 0);//executie sort
}
else {
// Parinte
close (p[0]);
close (p[1]);
wait (0);
wait (0);
}
}

Observatie: pentru a se nelege mai bine exemplul de mai sus, invitm cititorul s citeasc
din manualele Unix prezentarea apelului sistem dup2. Aici apelul dup2 are ca paramentru
un descriptor pipe.

3.2.3.

3.2.3.1.

Comunicarea ntre procese prin FIFO

Conceptul de FIFO

Cel mai mare dezavantaj al lui pipe sub Unix este faptul c poate fi utilizat numai n
procese nrudite: procesele care comunic prin pipe trebuie s fie descendeni din procesul
creator al lui pipe. Aceasta deoarece ntregii descriptori de citire/scriere din/n pipe sunt unici
i sunt transmii proceselor fiu ca urmare a apelului fork().
In jurul anului 1985 (Unix System V), a aprut conceptul FIFO ( pipe cu nume). Acesta este
un flux de date unidirecional, accesat prin intermediul unui fiier rezident n sistemul de
fiiere. Incepnd cu Unix System V, exist fiiere de tip FIFO. Spre deosebire de pipe,
fiierele FIFO au nume i ocup un loc n sistemul de fiiere. Din aceast cauz, un FIFO
73

poate fi accesat de orice dou procese, nu neaprat cu printe comun. Atenie ns: chiar dac
un FIFO exist ca fiier n sistemul de fiiere, pe disc nu se stocheaz nici o dat care trece
prin canalul FIFO, acestea fiind stocate i gestionate n buffer-ele nucleului sistemului de
operare!
Conceptual, canalele pipe i FIFO sunt similare. Deosebirile eseniale dintre ele sunt
urmtoarele dou:
suportul pentru pipe este o poriune din memoria RAM gestionat de nucleu, n timp
ce FIFO are ca suport discul magnetic;
toate procesele care comunic prin-un pipe trebuie s fie descendente ale procesului
creator al canalului pipe, n timp ce pentru FIFO nu se cere nici o relaie ntre
procesele protagoniste.
Crearea unui fifo se poate face folosind unul dintre apelurile:
int mknod (char *numeFIFO, int mod, 0);
int mkfifo (char *numeFIFO, int mod);

sau folosind una dintre comenzile shell:


$ mknod numeFIFO p
$ mkfifo numeFIFO

Prin stringul numeFIFO este specificat numele "fiierului" de tip FIFO.


Argumentul mod, n cazul apelurilor sistem, reprezint drepturile de acces la acest fiier.
In cazul apelului mknod, mod trebuie s specifice flagul S_IFIFO, pe lng drepturile de
acces la fiierul FIFO (se leag prin operatorul |). Acest flag este definit n
<sys/stat.h>.
Pentru crearea unui FIFO cu apelul sistem mknod, cel de-al treilea parametru este
ignorat (trebuie ns specificat, de aceea am pus 0).
De remarcat c trebuie specificat "p" (de la pipe cu nume), ultimul parametru al comenzii
shell mknod.

Menionm c cele dou apeluri de mai sus, dei sunt specificate de POSIX, nu sunt
amndou apeluri sistem pe toate implementrile de Unix. Astfel, pe FreeBSD sunt prezente
ambele apeluri sistem mknod() i mkfifo(), dar pe Linux i Solaris exist numai apelul
sistem mknod(), funcia de bibliotec mkfifo() fiind implementat cu ajutorul apelului
sistem mknod(). Cele dou comenzi shell sunt ns disponibile pe majoritatea
implementrilor de Unix. Sub sistemele Unix mai vechi, comenzile mknod i mkfifo sunt
permise numai super-user-ului. Incepnd cu Unix System V 4.3 ele sunt disponibile i
utilizatorului obinuit.
tergerea (distrugerea) unui FIFO se poate face fie cu comanda shell rm numeFIFO, fie cu
un apel sistem C unlink() care cere un descriptor pentru fiierul FIFO.
Odat ce FIFO este creat, el trebuie s fie deschis pentru citire sau scriere folosind apelul
sistem open. Precizarea sau nu a flagului O_NDELAY la apelul sistem open are efectele
indicate n tabelul urmtor.

74

Condiii
deschide
FIFO
read-only, dar nu
exist proces de
scriere n FIFO
deschide
FIFO
write-only, dar nu
exist proces de
citire din FIFO
citire din FIFO sau
din pipe, dar nu
exist date de citit

normal
setat O_NDELAY
ateapt pn cnd apare un proces care revine imediat fr a
semnala eroare
deschide FIFO pentru scriere

ateapt pn apare un proces pentru citire

revine
imediat
cu
semnalarea de eroare:
variabila errno va
deveni ENXIO
ateapt pn cnd apar date n pipe sau revine
imediat,
cu
FIFO, sau pn cnd nu mai exist proces ntoarcerea valorii 0
deschis pentru scriere. Intoarce numrul de
date citite dac apar noi date, sau 0 dac nu
mai exist proces de scriere
imediat,
cu
scrie n FIFO sau ateapt pn cnd se face spaiu disponibil, revine
pipe, dar acesta apoi scrie attea date, ct i permite spaiul ntoarcerea valorii 0
disponibil
este plin
Aceste reguli trebuie completate cu regulile de citire/scriere de la nceputul capitolului despre
comunicaii prin fluxuri de octei. De asemenea, nainte de a fi folosit, un canal FIFO trebuie
s fie n prealabil deschis pentru citire de un proces i deschis pentru scriere de alt proces.

3.2.3.2.

Exemplu: aplicare FIFO la comunicare client / server

Modelul de aplicaie client / server este clasic n programare. In cele ce urmeaz vom ilustra
o schem de aplicaii client / server bazate pe comunicaii prin FIFO. Pentru a se asigura
comunicarea bidirecional se folosesc dou FIFO-uri. Pentru partea specific aplicaiei, se
folosesc metodele client(int in, int out) i server(int in, int out).
Fiecare dintre ele primete ca i parametri descriptorii de fiiere, presupuse deschise, prin
care comunic cu partenerul.
In cele dou programe care urmeaz este schiat schema serverului i a clientului. Cele dou
programe presupun c cele dou canale FIFO sunt create, respectiv terse, prin comenzi Unix.
Programul server:
#include
#include
#include
#include
#include

<sys/types.h>
<sys/stat.h>
<sys/errno.h>
<stdio.h>
<unistd.h>

#include "server.c"
#define FIFO1 "/tmp/fifo.1"
#define FIFO2 "/tmp/fifo.2"
main() {
int

readfd, writefd;

75

- - - - - - - - - - - - - readfd = open (FIFO1, 0));


writefd = open (FIFO2, 1));
for ( ; ; ) { // bucla de asteptare a cererilor
server(readfd, writefd);
}
- - - - - - - - - - - - - close (readfd);
close (writefd);

}
Programul client:
#include
#include
#include
#include
#include

<sys/types.h>
<sys/stat.h>
<sys/errno.h>
<stdio.h>
<unistd.h>

#include "client.c"
extern int errno;
#define FIFO1 "/tmp/fifo.1"
#define FIFO2 "/tmp/fifo.2"
main() {
int

readfd, writefd;

- - - - - - - - - - - - - writefd = open (FIFO1, 1));


if ((readfd = open (FIFO2, 0));
client(readfd, writefd);
- - - - - - - - - - - - close (readfd);
close (writefd);
}

76

3.3. Interpretoare ale fiierelor de comenzi

3.3.1.

Funcionarea unui interpretor de comenzi shell

Un interpretor de comenzi (shell) este un program special care furnizeaz o interfa ntre
nucleul sistemului de operare Unix (kernel-ul) i utilizator. Din aceast perspectiv, a
asigurrii legturii ntre utilizator i sistem, un shell poate fi privit diferit:
1. limbaj de comand care asigur interfaa dintre calculator i utilizator. In momentul n
care un utilizator i deschide o sesiune de lucru, n mod implicit, un shell se
instaleaz ca interpretor de comenzi. Shell-ul afieaz la ieirea standard (asociat de
obicei unui terminal) un prompter, invitnd astfel utilizatorul s introduc comenzi
sau s lanseze n execuie fiiere de comenzi, eventual parametrizate.
2. limbaj de programare, ce are ca element de baz (element primitiv) comanda Unix
(similar semantic cu instruciunea de atribuire din limbajele de programare). Ca i
element primitiv de dirijare a succesiunii elementelor de baz este valoarea codului
de retur al ultimei comenzi executate: valoarea 0 nseamn true, valoare nenul
nseamn false (corespondentul din limbajele de programare clasice este condiia).
Shell-urile dispun de conceptele de variabil, constant, expresie, structuri de control
i subprogram. Spre deosebire de alte limbaje de programare, expresiile cu care
lucreaz shell-urile sunt preponderent iruri de caractere. In ceea ce privete cerinele
sintactice, acestea au fost reduse la minim prin eliminarea parantezelor de delimitare a
parametrilor, a diferitelor caractere de separare i terminare, a declaraiilor de
variabile, etc.
un shell lansat n execuie la deschiderea unei sesiuni de lucru va rmne activ pn la
nchiderea respectivei sesiuni. Odat instalat, acesta lucreaz conform algoritmului urmtor:
CtTimp (nu s-a nchis sesiunea)
Afieaz prompter;
Citete linia de comand;
Dac ( linia se termin cu '&' ) atunci
Creaz un proces i-i d spre execuie comanda
Nu ateapt ca execuia s se termine
Altfel
Creaz un proces i-i d spre execuie comanda
Ateapt s se termine execuia comenzii
SfDac
SfCtTimp

Este important s remarcm, din algoritmul de mai sus, cele dou moduri n care o comand
poate fi executat:
modul foreground - execuie la vedere. In acest gen de execuie sh lanseaz execuia
comenzii, ateapt terminarea ei dup care afieaz din nou prompterul pentru o nou
comand. Acesta este modul implicit de execuie al oricrei comenzi Unix.
modul background - execuie n fundal, ascuns. In acest gen de execuie sh lanseaz
procesul care va executa comanda, dar nu mai ateapt terminarea ei ci afieaz
imediat prompterul, oferind utilizatorului posibilitatea de a lansa imediat o nou

77

comand. Comada care se dorete a fi lansat n background trebuie s se ncheie cu


caracterul special '&'.
Intr-o fereastr (sesiune) de lucru Unix se pot rula oricte comenzi n background i numai
una n foreground. Iat, spre exemplu, trei astfel de comenzi, dou lansate n background - o
copiere de fiier (comanda cp) i o compilare (comanda gcc) i una n foreground - editare
de fiier (comanda vi):
cp A B &
gcc x.c &
vi H

3.3.2.

3.3.2.1.

Programarea n shell

Scurt prezentare a limbajului sh

In cele ce urmeaz vom prezenta gramatica limbajului sh cel mai simplu shell de sub Unix.
Vom pune n eviden principalele categorii sintactice, semantica / funcionalitatea fiecrei
astfel de categorii se deduce uor din context.
Vom considera urmtoarele convenii, pe care le folosim doar n scrierea regulilor gramaticii:
O categorie gramatical se poate defini prin una sau mai multe alternative de definire.
Alternativele se scriu cte una pe linie, ncepnd cu linia de dup numele categoriei
gramaticale, astfel:
categorieGramatical:
alternativa_1 de definire
---alternativa_n de definire
[ ]? Semnific faptul c, construcia dintre paranteze va aprea cel mult odat.
[ ]+ Semnific faptul c, construcia dintre paranteze va aprea cel puin odat.
[ ]* Semnific faptul c, construcia dintre paranteze poate s apar de 0 sau mai multe
ori.
Folosind aceste convenii, sintaxa limbajului sh (n partea ei superioar, fr detalii) este
descris n fig. 2.2.
Semnificaia unora dintre elementele sintactice din fig. 2.2 este:
cuvnt: secven de caractere diferite de caracterele albe (spaiu, tab)
nume: secven ce ncepe cu liter i continu cu litere, cifre, _ (underscore)
cifra: cele 10 cifre zecimale
O comanda sh poate avea oricare dintre cele 9 forme prezentate. Una dintre modalitile de
definire este cea de comandElementar, unde o astfel de comand elementar este un ir de
elemente, un element putnd fi definit n 10 moduri distincte. O legarePipe este fie o singur
comand, fie un ir de comenzi separate prin caracterul special '|'. In sfrit, listaCom este o
succesiune de legarePipe separate i eventual terminate cu simboluri speciale.
78

Se poate observa c, n conformitate cu gramatica de mai sus, sh accept i construcii fr


semantic! De exemplu, comand poate fi o comandElementar, care s conin un singur
element, format din >&-;. O astfel de linie este acceptat de sh, fiind corect din punct de
vedere sintactic, dei nu are sens din punct de vedere semantic.
Shell-ul sh are un numr de 13 cuvinte rezervate. Lista acestora este urmtoarea:
if then else elif fi
case in esac
for while until do done

Structurile alternative if i case sunt nchise de construciile fi, respectiv esac, obinute prin
oglindirea cuvintelor de start. In cazul ciclurilor repetitive, sfritul acestora este indicat prin
folosirea cuvntului rezervat done. Nu s-a folosit construcia similar corespunztoare lui do,
deoarece od este numele unui comenzi clasice Unix.
Incheiem acest subcapitol cu prezentarea sintaxei unor construcii rezervate, precum i a
unor caractere cu semnificaie special n shell-ul sh.
a) Construcii sintactice:
|
legare pipe
&&
legare andTrue
||
legare orFalse
;
separator / terminator comand
;;
delimitator case
(), {}
grupri de comenzi
< <<
redirectri intrare
> >>
redirectri ieire
&cifra, &specific intrare sau ieire standard
b) Machete i specificri generice:
*
nlocuiete orice ir de caractere
?
nlocuiete orice caracter
[...]
nlocuiete cu orice caracter din ...
Observaie: aceste machete i specificri generice nu trebuie confundate cu convenia
propus la nceputul subcapitolului pentru scrierea gramaticii limbajului sh

comand:
comandElementar
( listaCom )
{ listaCom }
if listaCom then listaCom [ elif listaCom then listaCom ]* [ else listaCom ]? fi
case cuvant in [ cuvant [ | cuvant ]* ) listaCom ;; ]+ esac
for nume do listaCom done
for nume in [ cuvant ]+ do listaCom done
while listaCom do listaCom done
until listaCom do listaCom done
comandElementar:
[ element ]+

79

listaCom:
legarePipe [ separator legarePipe ]* [ terminator ]?
legarePipe:
comanda [ | comanda ]*
element:
cuvnt
nume=cuvnt
>cuvnt
<cuvnt
>>cuvnt
<<cuvnt
>&cifra
<&cifra
<&>&separator:
&&
||
terminator
terminator:
;
&

3.4. Probleme propuse


I.

a. Descriei pe scurt funcionarea apelului sistem fork i valorile pe care le poate returna.
b. Ce tiprete pe ecran secvena de program de mai jos, considernd c apelul sistem
fork se execut cu succes? Justificai rspunsul.
int main() {
int n = 1;
if(fork() == 0) {
n = n + 1;
exit(0);
}
n = n + 2;
printf(%d: %d\n, getpid(), n);
wait(0);
return 0;
}

c. Ce tiprete pe ecran fragmentul de script shell de mai jos? Explicai funcionarea


primelor trei linii ale fragmentului.
1
2

80

for F in *.txt; do
K=`grep abc $F`

3
4
5
6

if [ $K != ]; then
echo $F
fi
done

II.

a. Se d fragmentul de cod de mai jos. Indicai liniile care se vor tipri pe ecran n ordinea n
care vor aprea, considernd c apelul sistem fork se execut cu succes? Justificai
rspunsul.
int main() {
int i;
for(i=0; i<2; i++) {
printf("%d: %d\n", getpid(), i);
if(fork() == 0) {
printf("%d: %d\n", getpid(), i);
exit(0);
}
}
for(i=0; i<2; i++) {
wait(0);
}
return 0;
}

b. Explicai funcionarea fragmentului de script shell de mai jos. Ce se ntmpl, dac


fiierul raport.txt lipsete iniial. Adugai rndul de cod care lipsete pentru generarea
fiierului raport.txt.
more raport.txt
rm raport.txt
for f in *.sh; do
if [ ! -x $f ]; then
chmod 700 $f
fi
done
mail -s "Raport fisiere afectate" admin@scs.ubbcluj.ro <raport.txt

81

4. Bibliografie general
1. ***: Linux man magyarul, http://people.inf.elte.hu/csa/MAN/HTML/index.htm
2. A.S. Tanenbaum, A.S. Woodhull, Opercis rendszerek, 2007, Panem Kiad.
3. Alexandrescu, Programarea modern in C++. Programare generic si modele de
proiectare aplicate, Editura Teora, 2002.
4. Angster Erzsbet: Objektumorientlt tervezs s programozs Java, 4KR Bt, 2003.
5. Bartk Nagy Jnos, Laufer Judit, UNIX felhasznli ismeretek, Openinfo
6. Bjarne Stroustrup: A C++ programozsi nyelv, Kiskapu kiad, Budapest, 2001.
7. Bjarne Stroustrup: The C++ Programming Language Special Edition, AT&T, 2000.
8. Boian F.M. Frentiu M., Lazr I. Tambulea L. Informatica de baz. Presa Universitar
Clujeana, Cluj, 2005
9. Boian F.M., Ferdean C.M., Boian R.F., Drago R.C., Programare concurent pe
platforme Unix, Windows, Java, Ed. Albastr, Cluj-Napoca, 2002
10. Boian F.M., Vancea A., Bufnea D., Boian R.,F., Cobrzan C., Sterca A., Cojocar D.,
Sisteme de operare, RISOPRINT, 2006
11. Bradley L. Jones: C# mesteri szinten 21 nap alatt, Kiskapu kiad, Budapest, 2004.
12. Bradley L. Jones: SAMS Teach Yourself the C# Language in 21 Days, Pearson
Education,2004.
13. Cormen, T., Leiserson, C., Rivest, R., Introducere n algoritmi, Editura Computer
Libris Agora, Cluj, 2000
14. DATE, C.J., An Introduction to Database Systems (8th Edition), Addison-Wesley,
2004.
15. Eckel B., Thinking in C++, vol I-II, http://www.mindview.net
16. Ellis M.A., Stroustrup B., The annotated C++ Reference Manual, Addison-Wesley,
1995
17. Frentiu M., Lazr I. Bazele programrii. Partea I-a: Proiectarea algoritmilor
18. Herbert Schildt: Java. The Complete Reference, Eighth Edition, McGraw-Hill, 2011.
19. Horowitz, E., Fundamentals of Data Structures in C++, Computer Science Press,
1995
20. J. D. Ullman, J. Widom: Adatbzisrendszerek - Alapvets, Panem kiado, 2008.
21. ULLMAN, J., WIDOM, J., A First Course in Database Systems (3rd Edition),
Addison-Wesley + Prentice-Hall, 2011.
22. Kiad Kft, 1998, http://www.szabilinux.hu/ufi/main.htm
23. Niculescu,V., Czibula, G., Structuri fundamentale de date i algoritmi. O perspectiv
orientat obiect., Ed. Casa Crii de Stiin, Cluj-Napoca, 2011
24. RAMAKRISHNAN, R., Database Management Systems. McGraw-Hill, 2007,
http://pages.cs.wisc.edu/~dbbook/openAccess/thirdEdition/slides/slides3ed.html
25. Robert Sedgewick: Algorithms, Addison-Wesley, 1984
26. Simon Kroly: Kenyernk Java. A Java programozs alapjai, Presa Universitar
Clujean, 2010.
27. Tmbulea L., Baze de date, Facultatea de matematic i Informatic, Centrul de
Formare Continu i Invmnt la Distan, Cluj-Napoca, 2003
28. V. Varga: Adatbzisrendszerek (A relcis modelltl az XML adatokig), Editura Presa
Universitar Clujean, 2005, p. 260. ISBN 973-610-372-2

82