I. Programare orientată pe obiecte … … … 9 [614089]

1
UNIVERSITATEA SPIRU HARET
FACULTATEA DE INGINERIE, INFORMATICĂ ȘI GEOGRAFIE

2

3

4

5
Cuprins

I. Programare orientată pe obiecte ………………………….. ………………………….. ………………. 9
1. Fundamente ale programării orientate pe obiecte ………………………….. ………………………….. …………… 9
1.1. Concepte POO: clasă, obiect (identificare unică, interfață, stare, mesaj), metodă ……………… 9
1.2. Principiile POO: în capsulare, moștenire, polimorfism ………………………….. …………………….. 10
2. Implementarea POO în C++ ………………………….. ………………………….. ………………………….. ………. 11
2.1. Definirea claselor ………………………….. ………………………….. ………………………….. ……………… 11
2.2. Constructori, destructori, obiecte C++ ………………………….. ………………………….. ……………… 13
2.3. Clase derivate: definire, accesul la membrii clasei, constructorii și destructorii în contextul
claselor derivate, tipuri de constructori. ………………………….. ………………………….. ………………………. 18
2.4. Metode virtuale și clase abstracte. ………………………….. ………………………….. ……………………. 20
2.5. Supraîncărcarea metodelor ………………………….. ………………………….. ………………………….. …. 22
2.6. Metode inline, funcții friend ………………………….. ………………………….. ………………………….. .. 24
2.7. Supraîncărcarea operatorilor ………………………….. ………………………….. ………………………….. . 26
2.7.1. Supraîncărcarea cu funcții operator membre ………………………….. ………………………….. . 27
2.7.2. Supraîncărcarea cu funcții operator de tip friend ………………………….. …………………….. 29
2.8. Fluxuri de intrare -ieșire în C++ ………………………….. ………………………….. ……………………….. 30
2.9. Tratarea excepțiilor ………………………….. ………………………….. ………………………….. ……………. 32
2.10. Programare generică în C++. Biblioteca standard de șabloane. ………………………….. …….. 33
3. Antrenamente C++ ………………………….. ………………………….. ………………………….. ………………………. 41
3.1. Noțiuni fundamentale ………………………….. ………………………….. ………………………….. …………….. 41
3.2. Studii de caz ………………………….. ………………………….. ………………………….. …………………………. 44
3.3. Probleme propuse spre rezolvare ………………………….. ………………………….. ………………………….. 53
Bibliografie ………………………….. ………………………….. ………………………….. ………………………….. ………… 56

II. Baze de date ………………………….. ………………………….. ………………………….. ………………………. 57
1. Noțiuni introductive în teoria bazelor de date ………………………….. ………………………….. ………………. 57
1.1. Noțiunile de bază de date, sistem de gestiune a bazei de date ………………………….. …………… 57
1.2. Noțiunile de entitate, relație, atribut ………………………….. ………………………….. …………………. 57

6
1.3. Construirea de diagrame entitate -relație ………………………….. ………………………….. ……………. 59
1.4. Tipuri de relații între entități ………………………….. ………………………….. ………………………….. .. 60
2. Baze de date relaționale ………………………….. ………………………….. ………………………….. ………………… 60
2.1. Noțiunile de bază de date relațională, sistem de gestiune a bazelor de date relaționale …………. 60
2.2. Regulile lui Codd ………………………….. ………………………….. ………………………….. …………………… 61
2.3. Compo nentele bazelor de date relaționale: ………………………….. ………………………….. …………….. 62
1) Structura relațională a datelor ………………………….. ………………………….. ………………………….. 63
2) Operatorii modelului relațional ………………………….. ………………………….. ……………………….. 63
3) Restricții de integritate ale modelului relațional ………………………….. ………………………….. … 65
2.4. Tipuri de constrângeri de integritate ………………………….. ………………………….. ……………………… 66
3. Proiectarea bazelor de date relaționale ………………………….. ………………………….. ………………………… 66
3.1. For mele normale: FN1; FN2; FN3 ………………………….. ………………………….. ……………………….. 67
4. Limbajul SQL (Structured Query Language) ………………………….. ………………………….. ……………….. 73
4.1. Structura lexicală a limbajului SQL ………………………….. ………………………….. …………………. 73
4.2. Operatori SQL ………………………….. ………………………….. ………………………….. ………………….. 73
4.3. Funcții definite în SQL ………………………….. ………………………….. ………………………….. ………. 74
4.4. Tipuri de date ………………………….. ………………………….. ………………………….. ……………………. 75
4.5. Categor ii de instrucțiuni SQL ………………………….. ………………………….. ………………………….. 76
5. Limbajul de definire a datelor (LDD) ………………………….. ………………………….. …………………………. 76
5.1. Comenzi (CREATE, ALTER, DROP) ………………………….. ………………………….. ………………….. 77
6. Limbajul de manipulare a datelor (LMD) ………………………….. ………………………….. ……………………. 81
6.1. Interogarea datelor (Comanda SELECT) ………………………….. ………………………….. ………………. 82
6.2. Adăugarea de noi tupluri (Comanda INSERT) ………………………….. ………………………….. ………. 91
6.3. Modificarea tuplurilor din tabele (Comanda UPDATE) ………………………….. ………………………. 94
6.4. Ștergerea tuplurilor din tabele (Comanda DELETE) ………………………….. ………………………….. . 96
7. Limbajul de control al datelor (LCD) ………………………….. ………………………….. ………………………….. 98
7.1. Asigurarea confidențialității și securității datelor ………………………….. ………………………….. ……. 98
7.2. Reluarea unor acțiuni în cazul unei defecțiuni ………………………….. ………………………….. ……….. 99
7.3. Garantarea coerenței datelor în cazul prelucrării concurente ………………………….. ………………… 99

7
8. Exerciții de fixare a noțiunilor ………………………….. ………………………….. ………………………….. ……… 101
Bibliografie ………………………….. ………………………….. ………………………….. ………………………….. ………. 111

III. Structuri de dat e ………………………….. ………………………….. ………………………….. ……………. 113
1. Noțiuni teoretice – prezentare ………………………….. ………………………….. ………………………….. ….. 113
1.1 Structuri de date statice: vectori, matrici ………………………….. ………………………….. …………. 113
1.2 Liste ………………………….. ………………………….. ………………………….. ………………………….. ….. 115
1.2.1 Liste implementate secvențial ………………………….. ………………………….. ………………… 115
1.2.2 Liste înlănțuite ………………………….. ………………………….. ………………………….. …………. 115
1.2.3 Liste dublu înlănțuite ………………………….. ………………………….. ………………………….. … 118
1.3 Stive ………………………….. ………………………….. ………………………….. ………………………….. ….. 119
1.3.1 Stiva secvențial ă ………………………….. ………………………….. ………………………….. ………. 119
1.3.2 Stiva simplu înlănțuită ………………………….. ………………………….. ………………………….. . 120
1.4 Cozi ………………………….. ………………………….. ………………………….. ………………………….. ….. 121
1.4.1 Coada secvențială ………………………….. ………………………….. ………………………….. …….. 122
1.4.2 Coada simplu înlănțuită ………………………….. ………………………….. …………………………. 124
1.4.3 Coada dublu înlănțuită ………………………….. ………………………….. ………………………….. . 126
1.5 Structuri arborescente – noțiuni fundamentale ………………………….. ………………………….. …. 126
1.6 Arbori binari, arbori binari de cǎutare ………………………….. ………………………….. …………….. 129
1.7 Sortarea și cǎutarea ………………………….. ………………………….. ………………………….. ………….. 131
1.7.1 Sortarea prin numărare ………………………….. ………………………….. ………………………….. 132
1.7.2 Sortarea prin inserție ………………………….. ………………………….. ………………………….. … 132
1.7.3 Sortarea prin metoda bulelor ………………………….. ………………………….. ………………….. 133
1.7.4 Sortarea prin interclasare ………………………….. ………………………….. ……………………….. 134
1.7.5 Sortarea rapidă ………………………….. ………………………….. ………………………….. …………. 137
1.7.6 Căut area ………………………….. ………………………….. ………………………….. ………………….. 137
2. Înțelegerea conceptelor ………………………….. ………………………….. ………………………….. ……………. 138
3. Aplicarea și utilizarea noțiunilor teoretice ………………………….. ………………………….. ……………… 142
Bibliografie ………………………….. ………………………….. ………………………….. ………………………….. ………. 149

8
IV. Sisteme de operare ………………………….. ………………………….. ………………………….. ………… 151
1. Organizarea structurală a sistemelor de calcul. Fundamente ………………………….. …………………. 151
2. Structura sistemelor de operare ………………………….. ………………………….. ………………………….. ……. 154
3. Gestiunea proceselor ………………………….. ………………………….. ………………………….. ………………….. 157
3.1. Fundamente ………………………….. ………………………….. ………………………….. ………………………… 157
3.2. Sincronizarea proceselor ………………………….. ………………………….. ………………………….. ………. 160
3.2.1. Metoda variabilei poartă ………………………….. ………………………….. ………………………….. …. 161
3.2.2. Metoda alternării ………………………….. ………………………….. ………………………….. …………… 161
3.2.3. Metoda lui Peterson ………………………….. ………………………….. ………………………….. ……….. 161
3.2.4. Metoda semafoarelor ………………………….. ………………………….. ………………………….. ……… 162
3.3. Comunicare între procese ………………………….. ………………………….. ………………………….. ……… 164
3.4. Planificarea proceselor ………………………….. ………………………….. ………………………….. …………. 165
4. Gestiunea memoriei interne. Memoria virtuală ………………………….. ………………………….. …………… 170
4.1. Generalități ………………………….. ………………………….. ………………………….. …………………………. 170
4.2. Structuri de date și algoritmi ………………………….. ………………………….. ………………………….. … 171
4.3. Metode elementare de gestiune a memoriei operative ………………………….. ……………………….. 173
4.4. Metode avansate de gestiune a memoriei operative ………………………….. ………………………….. . 174
4.4.1. Alocare continuă ………………………….. ………………………….. ………………………….. ……………. 174
4.4.2. Memoria virtuală ………………………….. ………………………….. ………………………….. …………… 175
5. Gestiunea memorie externe. Sisteme de fișiere. Tehnici input -output ………………………….. ………… 179
5.1. Gestiunea fișierelor ………………………….. ………………………….. ………………………….. ………………. 179
5.2. Gestiunea cataloagelor ………………………….. ………………………….. ………………………….. …………. 182
5.3. Drivere de Intrare/Ieșire (I/E) ………………………….. ………………………….. ………………………….. … 183
6. Protecție și securitate ………………………….. ………………………….. ………………………….. ………………….. 184
7. Interfețe utilizator ………………………….. ………………………….. ………………………….. ………………………. 185
8. Exerciții și probleme ………………………….. ………………………….. ………………………….. ………………….. 185
Bibliografie ………………………….. ………………………….. ………………………….. ………………………….. ………. 195

9
I. Programare orientată pe obiecte
Grigore Albeanu , Prof.univ.dr., Universitatea Spiru Haret,
Alexandru Averian , Senior Software Developer at Luxoft,
Costinela Luminița Defta , Lector univ. dr., Universitatea Spiru Haret

1. Fundamente ale programării orientate pe obiecte

Programarea orientată pe obiecte (POO) presupune o bună experiență în modelarea obiectuală. Întreg
procesul computațional are la bază gândirea orientată pe obiecte. Concepte fundamentale, tehnici
elementare, dar și modalități avansate de programare orientată pe obiecte vor fi prezentate în secțiunile
de mai jos.

1.1. Concepte POO: clasă, obiect (identificare unică, interfață, stare, mesaj), metodă

Clasele descriu caracteristicile de stare și comportamentale ale obiectelor. Distingem două tipuri de
caracteristici: atribute (specifică trăsăturile obiectelor) și metode (specifică comportamentul
obiectelor). Clasa este identificată printr -un nume unic î n cadrul unui proiect. Acest nume identifică un
tip de date și este utilizat pentru operații de declarare a tipului. Deci, o colecție de obiecte caracterizate
similar din punct de vedere informațional și comportamental trebuie să aparțină unei clase. Clasa este
o noțiune abstractă care definește un anumit tip de obiecte. O clasă reprezintă mulțimea obiectelor de
același tip.

Crearea unui obiect presupune indicarea clasei din care face parte, astfel identificându -se proprietățile
obiectului și modul în car e acestea obiectul se va comporta. Pentru un programator, un obiect contează
prin: identitate (trebuie să fie unică), interfață (partajată în comun cu alte obiecte de același tip prin
raportare la o clasă definitoare) și stare (valorile curente ale atribut elor).

Metodele unei clase realizează prelucrări asupra atributelor obiectului de destinație. Deoarece acesta
este unic, referirea caracteristicilor (atribute sau metode ) se face direct, utilizând numele acestora. O
metodă poate fi asemuită cu o funcție de prelucrare ce are fixat unul din parametri (obiectul curent).

Pentru a evita crearea unor obiecte neinițializate (cu o parte din atribute nedefinite), o clasă trebuie să
dispună de una sau mai multe metode speciale, de tip constructor , prin care se as igură inițializarea
tuturor atributelor la momentul creării unui obiect. Numele constructorilor este identic cu numele
clasei. Când există mai mulți constructori pentru aceeași clasă, ei sunt distinși prin numărul diferit de
argumente sau prin tipul diferi t al argumentelor. Pentru a fi utilizabili, constructorii trebuie să facă
parte din interfața clasei.

Clasele sunt de regulă asociate prin relații client -server. Orice clasă este construită pentru a oferi
anumite servicii unor obiecte din alte clase. Clas ele care oferă servicii se numesc clase server. Clasele
ale căror obiecte utilizează serviciile se numesc clase client. Foarte frecvent o clasă joacă ambele
roluri: oferă servicii altor clase (are rol de server), dar pentru aceasta utilizează serviciile al tor clase
(are rol și de client). Clasa server oferă drept servicii o anumită parte a caracteristicilor sale (atribute
sau metode). Aceste caracteristici sunt declarate printr -un cuvânt cheie (de obicei “public”) și
alcătuiesc o listă de servicii numită in terfața clasei. Clasa client este interesată de lista de servicii
(interfața) clasei server, nu și de algoritmii utilizați în implementare acestor servicii. Acele
caracteristici ale clasei server care sunt considerate critice pentru funcționarea corectă ar trebui să nu

10
poată fi accesate direct de clasele client. Ele sunt declarate prin cuvântul cheie “private”. Este
recomandat ca atributele (de stare) să nu facă parte din interfața clasei.

Există un tip special de clase client ale unei clase server, numite subclase ale serverului. Acestea sunt
derivate direct din clasa server și pentru implementarea lor este necesar accesul la anumite
caracteristici ale casei server. Pentru acest tip de acces, caracteristicile se declară prin cuvântul cheie
“protected”; o c aractetristică protected nu este accesibilă altor clase client ci numai celor derivate
(extinse).

Sintetic, o clasă poate fi reprezentată precum în Fig. 1, în care caracteristicile din interfață (specificate
public ) sunt marcate cu semnul +, cele specific ate prin protected cu semnul #, iar cele specificate prin
private cu -.

Figura 1. Diagrama unei clase

Clasa client utlizează serviciile clasei server trimițând mesaje către obiecte ale clasei server. Un mesaj
este un nume de caracteristică (în cele mai multe cazuri, o metodă ) din interfața clasei server. Dacă de
exemplu serverul are numele S, obiectul are numele ob, iar mesajul este o metodă cu numele mes, fără
parametri, expresia prin care se transmite mesaju l este ob.mes() . Obiectul ob se numește destinatar al
mesajului sau obiect curent al metodei mes. Ca urmare a transmiterii mesajului, obiectul ob răspunde
prin executarea implementării S::mes() , care prelucrează atributele obiectului curent ob. Efectul est e
similar cu evaluarea expresiei mes(ob) din programarea procedurală.

1.2. Principiile POO: încapsulare, moștenire, polimorfism

Principiul încapsulării presupune două niveluri de abordare: 1) ca metodă de concepție , încapsularea
se referă la capacitatea de a separa aspectele externe ale unui obiect (interfața), accesibile altor obiecte,
de aspectele interne ale obiectului (atribute interne, structuri de date, algoritmi), care sunt ascunse față
de celelalte obiecte; 2) ca implementare , la nivelul unui limbaj d e programare, încapsularea este
asigurată de exigențele sintactice specifice.

Posibilitatea de a reutiliza, prin adaptare, programele deja scrise, fără a interveni asupra textului sursă
deja existent, este unul din elementele cheie care justifică eficienț a tehnicilor de programare orientată
pe obiecte. Operațiile de tip cut-copy -paste -insert asupra textului sursă al funcțiilor definite într -un
program, se transformă, în viziunea POO, în operații de specializare a claselor deja definite. Prin
crearea unor i erarhii de clase, în care unele provin din altele prin preluarea și specializarea unor
caracteristici, POO oferă posibilitatea de adaptare a unor programe existente, în condițiile în care se
dispune de textul sursă al definițiilor claselor (fișierele antet ) și codul obiect al implementărilor.

Prin aplicarea principiului moștenirii se creează condiții pentru specializarea unei clase existente.
Există mecanisme simple prin care se poate defini o clasă nouă S ce preia ( moștenește ) caracteristicile
unei clase deja existente C (numită și superclasă sau clasă de bază ), specializează unele din aceste
caracteristici pentru a le adapta unor situații noi și adaugă alte caracteristici. Noua clasă se numește, în
acest caz, subclasă a clasei care a fost utilizată ca pun ct de plecare. Se spune în acest caz că S este

11
obținută prin derivare din clasa C sau, și mai sugestiv, prin specializarea clasei C. Relația de
specializare(derivare) se reprezintă prin diagrama din fig. 2 și este un caz particular de relație client –
server (aici C este server, iar S client).

Figura 2. Relația de specializare

Trebuie reținut că, în literatura de specialitate și printre programatori, sunt utilizați cel puțin șapte
termeni echivalenți în cadrul relației de specializare: 1) C clasă, S subcl asă; 2) C superclasă, S clasă;
3) C clasă de bază, S clasă derivată; 4) C clasă , S clasă specializată; 5) C clasă generalizată, S clasă;
6) C tip de date, S subtip de date; 7) C supertip de date, S tip de date.

Moștenirea poate fi simplă (clasa derivată preia atributele și metodele unei singure clase de bază), sau
multiplă (clasa derivată preia atributele și metodele mai multor clase de bază). Implementarea
moștenirii este realizată diferit în funcție de limbajul de programare utilizat. De exemplu, în lim bajul
Java este permisă moștenirea simplă, iar moștenirea multiplă este realizată indirect dintr -o clasă de
bază și una sau mai multe interfețe (aici cuvântul interfaă are alt sens decât cel prezentat mai sus).

Prin moștenire multiplă și indirectă se crea ză ierarhii de clase, care sunt grafuri orientate aciclice (în
cazul moștenirii simple avem un arbore orientat).

Specializarea poate fi realizată prin adăugarea de noi operații celor moștenite sau prin redefinirea
unora dintre operațiile moștenite. Specia lizarea orientată pe redefinirea operațiilor se află la baza
implementării principiului polimorfismului . Aplicarea principiului polimorfismului creează
posibilitatea ca un mesaj (în structura căruia intervine un nume de operație) să genereze răspunsuri
diferite, în funcție de contextul în care este formulat mesajul.

În POO se face diferență între supraîncărcare (overloading) și redefinire (overriding). Supraîncărcarea
se referă la existența mai multor metode sau constructori cu același nume, dar cu signatură diferită.
Redefinirea se referă la definirea unei metode cu același nume, dar intr -o clasă derivată. Constructorii
nu pot fi redefiniți, ei fiind legați de clasa în care sunt definiți.

2. Implementarea POO în C++

În cele ce urmează vom descoperi modul în care sunt implementate principiile POO în limbajul C++.

2.1. Definirea claselor

O clasă C++ încapsulează date și funcții (membrii clasei) care beneficiază de anumite niveluri de
protecție împotriva accesului extern. Metodele pot fi definite atât în interiorul clasei ( inline ) cât și în
afara acestora, folosind operatorul de rezoluție „::”. Forma generală a unei specificații de clasă este
următoarea:

class <Nume_clasă> [:<Lista_claselor_de_bază>] {
<Date și funcții particulare>
<Specifi cator de acces>:

12
<Date și funcții>
<Specificator de acces>:
<Date și funcții>
………..
<Specificator de acces>:
<Date și funcții>
} [<Lista de obiecte>];

unde:
 <Lista de obiecte> este opțională. Dacă există, ea declară obiecte din clasa definită.
 <Specificator de acces> este unul din cuvintele cheie: public , private și protected .
 <Lista_claselor_de_bază> indică, opțional, clasele de la care se pot moșteni atribute
informaționale și comportamentale .
 <Nume_clasă> este identificatorul C++ care denumește în mod unic clasa.
 <Date și funcții particulare>: Implicit, datele și funcțiile declarate într -o clasă sunt proprii
acelei clase, doar membrii săi având acces la ele.
 Odată utilizat un specificator, e fectul său este valabil până la întâlnirea altui specificator de
acces sau a sfârșitului declarației clasei.
 Folosind specificatorul de acces public , permitem funcțiilor sau datelor membre să fie
accesibile altor secțiuni ale programului care utilizează o biecte ale clasei. Revenirea la modul
privat de declarare a membrilor se realizează folosind specificatorul de acces private .
Specificatorul protected are implicații asupra vizibilității membrilor unei clase în cazul în
care se are în vedere moștenirea.
 Din punct de vedere practic se recomandă protecția totală a datelor (folosind private ) și
definirea unor metode de tip GET (pentru citirea stării obiectului), respectiv SET (pentru
modificarea stării obiectului) care vor fi specificate prin public .
 Modificat orii de acces pot fi utilizați atât în cadrul claselor cât și în cadrul structurilor.
Accesul este implicit public pentru structuri ( struct ) și private pentru clase.
 O clasă poate oferi acces nerestricționat la membrii proprii prin utilizarea cuvântului ch eie
friend în una din formele:
friend prototip_funcție;
sau
friend nume_clasă;
 În limbajul C++, structurile și uniunile reprezintă cazuri particulare ale claselor, putând avea
nu numai date membre, câmpuri de date, dar și funcții membre. Singura diferenț ă între
structuri și uniuni constă în faptul că la uniuni, pentru memorarea valorilor datelor membre se
folosește aceeași zonă de memorie. Deosebirea esențială dintre structuri și uniuni – pe de o
parte – și clase – pe cealaltă parte – constă în modul de a cces la membrii: la structuri și uniuni
membrii (datele si metodele) sunt implicit publici, iar la clase – implicit privați (fiind
încapsulați). Deci, lipsa unor modalități de protecție a datelor, face ca tipurile de date
introduse prin struct sau union să nu poată fi strict controlate în ceea ce privește operațiile
executate asupra lor.
 Datele membru se alocă distinct pentru fiecare instanță a clasei. Excepția de la această regulă
o constituie datele membru statice (introduse folosind cuvântul cheie static ), care există într –
un singur exemplar, comun, pentru toate instanțele clasei.

13
2.2. Constructori, destructori, obiecte C++

Constructorul este o funcție membru a clasei care are același nume cu clasa. Pentru fiecare obiect
declarat este apelat un constructor. Constructorul poate avea parametri formali, cu sau făra înițializare.
Pentru a putea fi utilizat, constructorul trebuie să fie declar at public .

Un constructor al unui obiect este apelat o singură dată pentru obiecte globale sau pentru cele locale de
tip static. Constructorul este o funcție fără tip ceea ce nu impune utilizarea cuvântului cheie void în
locul tipului. Pentru obiecte loca le constructorul este apelat de fiecare dată când este întâlnită
declararea acestora.

Dacă programatorul nu specifică un constructor explicit la definirea unei clase, la crearea unei instanțe
a clasei (declararea unui obiect) se folosește constructorul im plicit atașat de compilator fiecărei clase.
Constructorul implicit nu are parametri formali, ceea ce are drept consecință faptul că nu este permisă
inițializarea la declarare a datelor membre ale obiectelor. Constructorul implicit nu este generat în
cazul în care clasa are atașat un alt constructor fără parametri.

Spre deosebire de funcțiile obișnuite, constructorii pot avea o listă de inițializare (a se vedea Exemplul
2) pentru atributele clasei, de forma:

constructor(param constructor) : atribut_1(param ), …,
atribut_n(param) {…}

În C++ un obiect poate prelua starea unui obiect existent prin intermediul constructorului de copiere.
Deci, un constructor de copiere are rolul de a atribui datele unui obiect altuia. Altfel spus,
constructorul de copiere e ste un constructor care creează un obiect inițializându -l cu un obiect din
aceeași clasă, care a fost creat anterior Preluarea datelor obiectului care se copiază utilizează
specificarea prin referință:

<Clasa> <Nume clasa> :: <Nume constructor> (<Nume cl asa> & Obiect);

Constructorii de copiere se apelează numai la inițializare, nu și la atribuire. Mai precis, constructorul
de copiere este utilizat pentru: 1) inițializarea unui obiect din altul de același tip; 2) copierea unui
obiect pentru a -l transmite unei funcții; 3) copierea unui obiect pentru a -l returna dintr -o funcție.

Dacă un constructor de copiere nu este definit într -o clasă, compilatorul însăși definește unul. Dacă
clasa are variabile pointer și are unele alocări dinamice de memorie, atunci es te necesar să existe un
constructor de copiere.

Exemplul 1 . Clasa Persoana din diagrama prezentată în fig. 3, are codul C++:

14
class Persoana{
// interfata
public:
// are doi constructori si metoda de
afisare
Persoana(char *nume, char *prenume, int
an_nastere=2000); //ultimul parametru este
implicit
Persoana();
void afisare();
void set_an_nastere(int an_nastere);
//sfarsit interfata
protected :
// parte inaccesibila clientilor, cu
exceptia subclaselor
// are trei atribute
char *n, *p; // nume, pren ume
private:
int an; // an nastere
};

Figura 3. Diagrama clasei Persoana

Complementul constructorului este destructorul . De multe ori un obiect trebuie să efectueze anumite
acțiuni când este distrus (de exemplu, trebuie să elibereze eventualele zone de memorie alocate de
către constructor). Este evident faptul că obiectele locale sunt distruse la părăsirea blocului în care
apar, iar obiectele globale la terminarea programului. Când este distrus un obiect, atunci este apelat
destructorul clasei definitoare.

Destructorul are același nume cu constructorul, dar precedat de un caracter ~. O clasă are un singur
destructor și ac esta nu poate avea parametri formali. Atât constructorul cât și destructorul, în C++, nu
pot să returneze valori.

În C++, obiectele pot fi create static sau dinamic. Sintaxa pentru varianta statică este:
<Clasa> <Obiect>[(<Lista de valori>)];
sau
<Clasa> <Obiect>[=<Valoare>];
Sintaxa ne arată că odată cu crearea instanței se poate face și inițializarea datelor membre ale
obiectului cu ajutorul constructorilor parametrizați.

În limbajul C, alocarea dinamică a memoriei este realizată cu ajutorul funcțiior malloc() și free(). Din
motive de compatibilitate și nu numai, aceste funcții sunt valabile și în C++. Totodată, C++ are un
sistem alternativ de alocare dinamică bazat pe operatorii new și delete . Sintaxa generală pentru new și
delete este:
<Pointer>=new <Tip>;
delete <Pointer> ;
unde <Pointer> este o variabilă pointer, compatibilă ca tip cu <Tip>. Așadar, <Pointer> poate păstra
adresa către zona de memorie în care încap date având tipul <Tip>. Operatorul delete trebuie folosit
doar cu un pointer valid, alocat deja prin utilizarea operatorului new. În caz contrar, rezultatele sunt
imprevizibile.

15
În plus, operatorul new permite inițializarea memoriei alocate unui tip de bază cu o valoare dată,
utilizând sintaxa:
<Pointer>= new <Tip> (<Valoare_inițială>);
// ca în exemplul: int *p; p = new int(10); .
Matricile nu pot fi inițializate în timpul alocării dinamice ce poate fi realizată folosind sintaxa:
<Ponter>= new <Tip> [Dimensiune];
Eliberarea memoriei alocate matrice i se va realiza printr -o construcție ce respectă sintaxa:
delete [ ] <Pointer>.
Sintaxa generală pentru declararea pointerilor la obiecte este:
<Pointer_la_obiect>= new <Clasa>;
Dacă <Clasa> are constructor parametrizat atunci se poate folosi sintaxa :
<Pointer_la_obiect>= new <Clasa>(<Lista_valori>);
pentru a realiza și inițializarea obiectului.

Alte aspecte importante:
1) Fiecare funcție membră posedă un argument ascuns, numit this, argument transmis în mod automat
de către compilator. Aceas ta variabilă (locală funcțiilor membru) reprezinta pointerul către obiectul
curent (cel care apelează metoda).
2) Obiectele dinamice nu se distrug automat, deoarece doar programatorul deține informația despre
durata de viață a unui astfel de obiect.
3) Pen tru operatii cu pointeri exista câteva reguli de sintaxă:
1. Pentru preluarea adresei unui membru al clasei se poate folosi operatorul ‘&’ urmat de numele
clasei, operatorul d e rezoluție și numele membrului :
pointer_membru =& clasa :: membru;
2. Pentru accesul l a un membru după preluarea adresei, trebuie indicat un obiect al clasei și
numele variabilei pointer, folosind unul dintre operatorii specializați :
a. Operatorul .* dacă se specifică un obiect al clasei;
b. Operatorul ->* dacă se specifică un pointer de obiect.

Exemplul 2 . Clasa Complex cu un constructor cu parametri, un constructor de copiere și obiecte de tip
Complex definite în mod static.

Class Complex {
private:
float Re;
float Im;
public:
Complex (float a, float b); //constructor cu parametri
Complex (Complex & z); //constructor de initializare
// Alternativ putem defini un constructor cu lista de initializare
Complex :: Complex (float r, float i): Re(r), Im(i) { }
float get_parte_reala();
float get_parte_imaginara();
void set_par te_reala(float a);
void set_parte_imaginara(float b);
};
Complex :: Complex (float a, float b){ // definire in afara clasei,
utilizare this
this -> Re = a;

16
this -> Im = b;
}
Complex :: Complex (Complex & z){ // definire in afara clasei
Re = z.R e;
Im = z.Im;
}
// Codul altor functii membre este omis
int main(void){
Complex z1(2.0, 3.0);
Complex z2 = z1; // prin copiere – echivalent cu Complex z2(z1);
…cout << z1.get_parte_reala() << “+ i*” <<
z1.get_parte_imaginara();
…cout << z2.get_parte_reala() << “+ i*” <<
z2.get_parte_imaginara();
}

Exemplul 3. O nouă definiție a clasei Persoana în care utilizăm alocare dinamică și specificăm
destructorul. Deduceți diagrama noii clase.

Class Persoana {
private:
char *nume;
int varsta;
public:
Persoana(char *n, int v){ // Constructor cu parametri definit
inline
nume = new char [strlen(n)+1];
strcpy(nume, n);
varsta = v;
}
Persoana(const Persoana &p){// Constructor de copiere definit
inline
nume = new char[strlen(p.nume)+1];
strcpy(nume, p.nume);
varsta = p.varsta;
}
~Persoana(){ //Destructor definit inline
delete nume;
}
void print(){ // Metoda definita inline
cout << "Numele persoanei:" << nume << endl;
cout << "Varsta persoanei:" << varsta << endl;
}
void set_Nume(char *n){ // Metoda definita inline
if(strlen(nume)<strlen(n)){
delete nume;
nume = new char [strlen(n)+1];
}
strcpy(nume, n);

17
}
void set_Varsta(int v){ // Metoda definita inline
varsta = v;
}
}; //Final definire clasă Persoana

Cea mai obișnuită formă de constructor de copiere este prezenta tă în exemplul de mai jos.

Exemplul 4 . Un exemplu de clasă cu doi constructori și destructor. Programul este compus din
funcțiile main și afisare și utilizează obiecte ale clasei definite.

#include <iostream>
using namespace std;
class Linie {
public:
int getLungime( void ); //Metoda GET
Linie(int lungime); // constructor uzual
Linie(const Linie &ob); // constructor de copiere
~Line(); // destructor
private:
int *adr;
};
Linie::Linie(int lung) {// primul constructor
adr = new int;
*adr = lung;
}
Linie::Linie(const Linie &ob) {
cout << "Constructor de copiere." << endl;
adr = new int;
*adr = *ob.adr; // copierea valorii
}
Linie::~Linie(void ) { // Destructor
cout << "Eliberarea memoriei alocate de constructor!" << endl;
delete adr;
}
int Linie::getLungime(void) { // Metoda GET – implementare
return *adr;
}
void afisare(Linie ob) { // functie externa clasei
cout << "Lungimea lini ei: " << ob.getLungime() << endl;
}

// Functia main pentru testare
int main(void) {
Linie linia1(10); afisare(linia1);
Linie linia2 = linia1; afisare(linia2);
return 0;
}

18
2.3. Clase derivate: definire, accesul la membrii clasei, constructorii și destructorii în contextul
claselor derivate, tipuri de constructori.

În C++ putem deriva o clasă din alta clasă de bază sau din mai multe clase. În primul caz, sintaxa este
următoarea:
class Clasa_Derivată : [modificatori de acces] Clasa_de_Bază { ….
};

În <Lista_claselor_de_bază>, clasele care fac obiectul moștenirii/derivării, apar precedate, eventual,
de un modificator de protecție și sunt separate între ele prin virgulă :
class Clasa_Derivată : [modificatori de acces] Clasa_de_Bază1,
[modificatori de acces] Clasa_de_Bază2,
[modificatori de acces] Clasa_de_Bază3
{….};
Modificatorii de protecție utilizați în <Lista_claselor_de_bază> definesc protecția în clasa derivată a
elementelor moștenite. Dacă lipseste modificatorul de protecție, atunci e considerat implicit private .

Notăm prin TACB – tipul de acces la clasa de bază, prin MPCB – modificatorul de protecție asociat
clasei de bază la definirea noii clase, iar prin ACD – accesul la membrii clase i derivate. Sunt valabile
următoarele reguli:
R1: Dacă TACB = “private” și MPCB = “private” atunci ACD = “interzis”;
R2: Dacă TACB = “protected” și MPCB = “private” atunci ACD = “private”;
R3: Dacă TACB = “public” și MPCB = “private” atunci ACD = “private” ;
R4: Dacă TACB = “private” și MPCB = “public” atunci ACD = “interzis”;
R5: Dacă TACB = “protected” și MPCB = “public” atunci ACD = “protected”;
R6: Dacă TACB = “public” și MPCB = “public” atunci ACD = “public”;
Deci: 1) clasa derivată are acces la elementele clasei de bază aflate sub incidența accesului
protected /public ; 2) dacă la definirea clasei derivate se utilizează modificatorul de protecție private ,
atunci elementele protejate în clasa de bază prin protect ed sau public devin protejate private în clasa
derivată; deci inaccesibile unor clase care s -ar deriva eventual din clasa derivată; 3) Este posibil să se
moștenească o clasă de bază ca protected . Când se procedează astfel, toți membrii public și
protected ai clasei de bază devin membri protected ai clasei derivate.

Observații:
1. Prin moștenirea caracteristicilor este economisit timp de proiectare – implementare și este
încurajată reutilizarea programelor care au fost temeinic testate anterior. Implementator ul
clasei specializate nu are nevoie decât de interfața clasei de bază și de codul obiect al
implementării.
2. Un obiect al clasei specializate aparține și clasei de bază; invers nu este adevărat.
3. Este important să se facă distincție între relațiile “obiectul s este un obiect b” și “obiectul s are
un obiect b”. Relația este se modelează prin specializare iar relația are prin compunere.
Relația “obiectul s este un b” se implementează prin moștenire; relația “obiectul s are un b” se
implementează prin agregare.
4. Orice pointer la un obiect al clasei specializate poate fi convertit la pointer către un obiect din
clasa de bază. Un pointer de o anumită clasă poate fi convertit în mod explicit la un pointer de
clasă specializată dacă valoarea sa referă un obiect al cla sei specializate.
5. Crearea unei clase specializate nu afectează în nici un fel textul sursă sau codul obiect al clasei
(claselor, în cazul moștenirii multiple) de bază.

19
6. Modificările operate asupra unei clase server nu necesită schimbări ale claselor client, cu
condiția ca aceste modificări să nu afecteze signatura interfeței clasei server.
7. Există trei contexte în care o metodă client poate accesa un obiect server pentru a -i transmite
un mesaj:
a. serverul este o variabila locală a metodei client (cea mai simplă din punctual de
vedere al comunicării între obiecte: serverul este accesibil doar metodei client);
b. serverul este un atribut al clasei din care face parte metoda client (serverul este
accesibil tuturor metodelor clasei precum și claselor și funcțiilor prie tene), context
întâlnit la reutilizarea codului;
c. serverul este un parametru al metodei client (serverul este accesibil metodei client dar
și metodelor care au activat -o).
8. La compunere (agregare), constructorii din clasele membre se apelează înaintea
const ructorului clasei ce conține membrii. Constructorii și destructorii sunt funcții membre
care nu se moștenesc, întrucât aceștia posedă numele clasei. La creerea și inițializarea unui
obiect dintr -o clasă derivată se apelează implicit, mai întâi constructori i claselor de bază (în
ordinea în care apar în declarația clasei derivate) și apoi constructorul clasei derivate.
9. La distrugerea unui obiect al clasei derivate, mai întâi este apelat destructorul clasei derivate,
și apoi destructorii claselor de bază, în o rdinea inversă celei în care apar în declarația clasei
derivate.

Figura 4. Exemplu de relație de specializare

Exemplul 5. Considerăm Clasa Student ca fiind derivată din clasa Persoana (din Exemplul 1, Fig. 3)
cu diagrama din Fig. 4. Codul C++ al clase Student este:
class Student: public Persoana{
public:
Student();
Student(char *nume, char *prenume, char *universitate, int
an_nastere=1984);
void afisare();
protected:
char *univ;
};

20
Se observă că în listele de inițializare ale constructorilor Student este utilizat constructorul clasei
Persoana . Deoarece constructorul fără argumente Persoana ()din lista de inițializare a constructorului
fără parametri Student () pune anul de naștere la v aloarea 2000, acesta trebuie modificat deoarece prin
lipsă, anul de naștere al unui student trebuie să fie 1984. De remarcat că acesta nu poate fi modificat
prin referire directă an=1984, deoarece acesta este declarat cu specificatorul private . El nu este
accesibil nici chiar clienților care specializează server -ul, cum este cazul aici. Modificarea anului este
însă posibilă prin metoda set_an_nastere (int), prevăzută special pentru asfel de situații. În
implementarea metodei Student::afisare ()s-a utilizat me toda de afișare a clasei Persoana (prin
expresia Persoana::afisare () în care apare operatorul de rezoluție ::)

Exemplul 6. Considerăm clasa Cerc definită prin centrul (Xc, Yc) și raza R. Exemplificăm definirea
constructorilor supuși supraîncărcării la nivelul clasei Cerc.

Class Cerc {
private:
double Xc;
double Yc;
double R;
public:
Cerc(){Xc = 0.0; Yc = 0.0; R = 1.0;} // cercul trigonometric
Cerc() Xc(10.0), Yc(10.0), R(100.0){} // constructor cu lista
Cerc(double xC, double yC, double raza){
Xc = xC;
Yc = yC;
R = raza;
} // constructor cu parametri
Cerc(Cerc & C){
Xc = C.Xc;
Yc = C.Yc;
R = C.R;
} // Constructor de copiere
void metoda1(); //definita mai tarziu folosind rezolutia
void metoda1 (double a, double b, double r); // supraincarcare
metoda1
double metoda2(); // definita mai tarziu folosind rezolutia
double metoda2(double r); //supraincarcarea metoda2
}

În POO, virtualizarea și abstractizarea sunt tehnici de proiectare și programare care fac diferența între
arhitecții POO.

2.4. Meto de virtuale și clase abstracte.

În programarea orientată obiect, o funcție virtuală sau o metodă virtuală este o funcție sau o metodă a
cărei comportare poate fi redefinită într -o clasă derivată printr -o funcție cu aceeași signatură. Acest
concept este o parte importantă a polimorfismului specific programării orientate obiect. Astfel, o
metodă virtuală este o funcție declarată virtual în clasa de bază și redefinită într -un lanț de derivare
asociat respectivei clase de bază.

21
Clasele care conțin metode vir tuale pure sunt denumite "abstracte" și nu pot fi instanțiate direct. În
practica POO, o clasă abstractă este o clasă care este proiectată pentru a fi utilizată în mod explicit ca o
clasă de bază. O clasă care are ca membrii doar funcții virtuale pure se n umește interfață.

O funcție virtuală pură este o funcție care este declarătă în clasa de bază și care impune clasei derivate
să o implementeze. Prototipul unei funcții virtuale pure este:
virtual tip nume_functie(parametri)=0;
Constructorii nu pot fi fun cții virtuale. Destructorii pot fi virtualizați. Dacă un destructor nu este
declarat virtual atunci numai memoria alocată în clasa de bază va fi eliberată. Prin urmare, destructorul
clasei de bază fiind virtual, atunci vor fi apelați destructorii claselor derivate.

Exemplul 7. Virtualizare și abstractizare folosind forme geometrice. Să presupunem că avem mai
multe clase care reprezintă forme geometrice: Punct, Cerc, Cilindru, derivate din clasa de bază Figura.
Fiecare dintre aceste clase trebuie să permită afișarea numelui formei pe care o reprezintă și alte
informații specifice precum coordonate, raza, înalțime, arie, volum.

class Figura{
public:
virtual double aria() const {return 0.0;}
virtual double volumul() const {return 0.0;}
//functii virtuale pure suprascrise in clasele derivate
virtual void AfisareNume() const = 0;
virtual void Afisare() const = 0;
};
class Punct : public Figura{
public:
Punct() x(0), y(0) {}; //constructor implicit cu lista de
initializare
Punct (int x = 0, int y = 0);
void setPunct( int, int); //fixeaza coordonatele
int getX() const { return x; } //inline – obtine abscisa x
int getY() const { return y; } //inline – obtine ordonata y
virtual void afisareNume() const {cout << "Punct: ";} //inline
virtual void afisare() const;
private:
int x, y; //x si y sunt coordonatele punctului
};
Punct::Punct( int a, int b) {setPunct(a, b);}
void Punct::setPunct( int a, int b){x = a; y = b;}
void Punct::afisare() const {cout << '(' << x << ", " << y << ')'; }
class Cerc : public Punct{
public:
Cerc(double r = 0.0, int x = 0, int y = 0); //constructor
implicit
void setRaza( double); //fixeaza raza
double getRaza() const; //obtine raza
virtual double aria() const; //calculeaza aria
virtual void afisareNume() const {cout << "Cerc: ";}
virtual void afisare() const;

22
private:
double r;
};
Cerc::Cerc( double r, int a, int b) : Punct(a, b){ setRaza(r); }
void Cerc::setRaza( double raza){ r = (raza > 0 ? raza : 0);}
double Cerc::getRaza() const { return r; }
double Cerc::aria() const {return 3.14159*r*r; }
void Cerc::afisare() const{
Punct::afisare();
cout << "; Raza = " << r;
}
class Cilindru : public Cerc {
public:
Cilindru( double h = 0.0, double r = 0.0, int x = 0, int y = 0);
void setH(double);
double getH() const;
virtual double aria() const;
virtual double volumul() const;
virtual void afisareNume() const {cout << "Cilindru: ";}
virtual void afisare() const;
private:
double h;
};
Cilindru::Cilindru( double h, double r, int x, int y) : Cerc(r, x,
y){setH(h);}
void Cilindru::setH( double a){ h = (a >= 0 ? a : 0); }
double Cilindru::getH() const {return h;}
double Cilindru::aria() const{ return 2*Cerc::aria()+2*3.14159*r*h;}
double Cilindru::volumul() const {return Cerc::aria()*h;}
void Cilindru::afisare() const{Cerc::afisare(); cout << "; Inaltimea
= " << h;}

2.5. Supraîncărcarea metodelor

Polimorfismul este capacitatea unor entități de a lua forme diferite. În C++ se întâlnesc două forme de
poliformism:
1) Polimorfismul parametric – mecanismul prin care putem defini o metodă cu același nume în
aceași clasă (am văzut constructori pâna acum, da r este valabil pentru orice metode cu
excepția destructorilor), funcții care trebuie să difere prin numărul și / sau tipul parametrilor.
Selecția funcției se realizează la compilate (legarea timpurie (early binging)).
2) Polimorfismul de moștenire – mecanismu l prin care o metodă din clasa de bază este redefinită
cu aceiași parametri în clasele derivate. Selecția funcției se va realiza la executare (legarea
întârziată (late binding, dynamic binding, runtime binding)). În limbajul C++ este implementat
cu ajutoru l funcțiilor virtuale

În C++ nu numai metodele (funcțtile membre ale claselor), dar și funcțiile specifice programării
procedurale se pot supraîncărcă, cu excepția funcției main . Este esențial ca numărul și / sau natura
parametrilor să difere (să aibă sig naturi diferite).

23
Exemplul 8 . Suma elementelor unui tablou se calculează folosind același algoritm. Dacă tipul datelor
diferă, atunci operațiile aritmetice diferă și deci se vor utiliza componente diferite ale prcesorului.
Funcția sum este implementată at ât pentru numere întregi, cât și pentru numere în virgulă mobilă.

int sum(int * tab, int nr_elemente){
int rez = 0;
int k;
for (k = 0; k < nr_elemente; k++) rez += tab[k];
return rez;
}
double sum(double * tab, int nr_elemente){
double rez = 0.0;
int k;
for (k = 0; k < nr_elemente; k++) rez += tab[k];
return rez;
}
int main(){ //exemplu de utilizare
int x[10]={1,2,3,4,5,6,7,8,9,10};
double y[3]={3.14,2.71,6.28};
cout << "suma valorilor int este " << sum(x,10 )<< endl;
cout << "suma valorilor double este " << sum(y,3) << endl;
return 0;
}

Exemplul 9 . Aici este creată o clasă fără explicitarea constructorilor și a destructorului. În plus,
metoda sch este supraîncărcată. Studiați și executați codul de mai jos. Reimp lementați folosind
programare generică (vedeți secțiunea 2.10).

#include <iostream>
class util{
public:
void sch(int *a, int *b) {int temp; temp = *a; *a = *b; *b =
temp;}
void sch(float *a, float *b) {float temp; temp = *a; *a = *b; *b
= temp;}
void sch(double *a, double *b) {double temp; temp = *a; *a = *b;
*b = temp;}
};
int main(){
int a1 = 640, b1 =480;
float a2 = 1.41, b2 = 1.73;
double a3 = 3.14, b3 = 12.71;
util m;
std::cout << a1 << ", " << b1 << std::endl;
m.sch(&a 1, &b1);
std::cout << a1 << ", " << b1 << std::endl;
std::cout << a2 << ", " << b2 << std::endl;
m.sch(&a2, &b2);
std::cout << a2 << ", " << b2 << std::endl;

24
std::cout << a3 << ", " << b3 << std::endl;
m.sch(&a3, &b3);
std::cout << a3 << ", " << b3 << std::endl;
return 0;
}

Exemple privind polimorfismul parametric au fost întâlnite la constructorii din sectiunile anterioare.
Polimorfismul de moștenire a fost arătat prin exemplul 7 în cadrul unei ierarhii de clase. Alte exemple
de supra încărcare vor fi prezentate în secțiunea 2.7.

2.6. Metode inline, funcții friend

Prin inline specificăm compilatorului necesitatea substituirii codului funcției oriunde îi apare numele.
Astfel, la apelul funcțiilor compilatorul decide dacă funcția este substi tuită sau este un subprogram.

Considerăm codul:

inline int aduna(int x, int y) { return x+y; }
int calc(){
int p =3; int q=5;
int rez = add(p, q); // aici va avea loc substituire
return rez;
}

Atunci linia int rez = aduna(p, q); va fi îlocuită cu int rez = p+q;

Toate funcțiile membre definite în interiorul unei clase sunt automat inline, chiar dacă nu este folosit
cuvântul cheie ca specificator, precum în secvența de cod:

class Calculator {
private:
int rez;
public:
int getRez() const {
return rez;
}
}; // atunci când codul metodei este foarte scurt

Cuvântul cheie inline poate fi folosit și în afara clasei atunci când codul este scurt:

class Calculator {
private:
int rez;
public:
int getRez() const;
}
inline int Calculator :: getRez() const {return rez;}

25
O funcție prietenă ( friend ) a unei clase este definită în afara domeniului acestei clase, dar are dreptul
de a accesa toți membrii privați și protejați ai clasei. Ch iar dacă prototipurile pentru funcțiile friend
apar în definiția clasei, prietenii nu sunt funcții membre. O entitate friend poate fi o funcție, un șablon
de funcție sau o funcție membru, sau o clasă sau un șablon de clasă, caz în care întreaga clasă și to ți
membrii săi sunt de tip friend .

Pentru a declara o funcție friend pentru o clasă, prototipul funcției din definiția clasei trebuie precedat
de cuvântul cheie friend:

class A { … alte elemente ale clasei
friend <tip> <identificator_functie> ([Lista-de-argumente]);
};

Declarea unei familii de clase care să aibă acces la entitățile clasei A respectă sintaxa:

class A { … alte elemente ale clasei
friend class lista_claselor_separate_prin_virgula;
};

Exemplul 10. Functia suma afișează rezulta tul adunării numerelor stocate de obiectele din clasele A și
B. Prin executarea codului de mai jos se va obține valoarea 1120 (= 640 + 480).

#include <iostream>
using namespace std;
// declaratie forward a clasei B
class B;
class A {
private:
int x;
public:
A(): x(640) { } //constructor cu lista de initializare
friend int suma(A, B); // functie friend pentru clasele A si B
};

class B {
private:
int y;
public:
B(): y(480) { }//constructor cu lista de initializare
friend int suma(A , B); //aici argumentele sunt obiecte
};

int suma(A x, B y){ return (x.x + y.y);}
int main(void){
A a;
B b;
Cout << "Suma: "<< suma(a, b);
return 0;
}

26

Exemplul 11 . Clasa B este prietenă cu clasa A. Invers nu este valabil.

#include <iostream>
using namespace std;
class A{
private:
double xa;
double ya;
public:
A(double x, double y){xa = x; ya = y;}
friend class B; //poate fi acordat accesul doar metodei print
};
class B{
private:
int p;
public:
B(int q) {p = q;}
void print(A &a){
double t;
switch(p){
case 1: { t = (a.xa + a.ya)/2.0; // media aritmetica
std::cout << t << '\n'; break;
}
case 2: { t = (a.xa > a.ya)? a.xa : a.ya; // maximul
std::cout << t << ' \n'; break;
}
} //sf. switch
} // sf. print
};

int main(){
A a(640.00, 480.00);
B b(1), c(2);
b.print(a); //media aritmetica
c.print(a); // maximul
return 0;
}

În loc de a defini prietenia pentru întreaga clasă putem defini prietenia doar pentru o anumită metodă a
clasei, caz în care folosim rezoluția:

friend B:: void prin t (A &a);

2.7. Supraîncărcarea operatorilor

Polimorfismul este realizat în C++ și prin supraîncărcarea operatorilor, o tehnică de programare foarte
interesantă și cu implicații practice. Când un operator este supraîncărcat, el capătă o semnificație
suplimentară relativ la o anumită clasă fără să -și piardă vreunul din înțelesurile inițiale. Mai sus au fost

27
utilizați operatorii << și >> supraîncărcați în fișierul iostream.h, care acționează, de obicei ca operatori
de deplasare.

Operatorii aritmetici precum + și / sunt deja supraîncărcați în C / C ++ pentru diferite tipuri
încorporate. De exemplu 1/3 == 0, iar 1.0/3 == 0.3333. Operatorul = și operatorul & sunt
supraîncărcați implicit pentru fiecare clasă, astfel încât acestea pot fi folosite pentru fiecare obiect de
clasă.
• operatorul = efectuează o c opie a datelor membre.
• operatorul & returnează adresa obiectului în memorie.

Prin supraîncărcarea operatorilor nu se poate schimba aritatea (signatura), prioritatea sau
asociativitatea operatorilor, acestea fiind elemente predefinite în limbaj.

Supraî ncărcarea operatorilor se realizează cu ajutorul unor funcții membre sau prietene ( friend ).
Numele acestor funcții se compune din cuvântul cheie operator și unul sau mai multe caractere care
definesc operatorul care se supraîncarcă. Pentru operatorii new și delete , trebuie să existe cel puțin un
spațiu între cuvântul cheie operator și caracterele care definesc operatorul de supraîncărcat.

Deci, orice metodă al cărei nume este de forma: operator <operator_C ++> se spune că supraîncărcă
operatorul specificat . Când se supraîncarcă un operator, compilatorul va apela funcția în loc să
interpreteze operatorul în mod normal. Sunt supraîncărcabili următorii operatori C++:

+ – * / % ^ & |
~ ! , = < > <= >=
++ – << >> == != && ||
+= -= /= %= ^= & = |= *=
<<= >>= [ ] ( ) -> ->* new, new []
delete, delete []

Operatorii C++ care nu pot fi încărcați sunt: . (selecția), :: (rezoluția), sizeof , typeid , dereferențierea .*
(pointer la membrul clasei) și ?: (decizia). Felul în care sunt scrise funcțiile operator diferă pentru cele
de tip membru de cele de tip friend .

2.7.1. Supraîncărcarea cu funcții operator membre

Funcțiile operator membru au sintaxa de im plementare:
<Tip_returnat> <Nume_clasă>:: operator # (<Lista_argumente>)
{//Operații specifice }
unde, # specifică caracterele ce definesc operatorul C++ care se supraîncarcă. Funcțiile operator
membre au un singur parametru sau nici unul. În cazul în care au un parametru, acesta se referă la
operandul din dreapta al operatorului. Celălalt operand ajunge la operator prin intermediul pointerului
special this.

Observație . Nu se pot utiliza funcții operator membru pentru a supraîncărca un operator dacă
operand ul stâng nu este un obiect al clasei respective. În acest caz trebuie utilizate funcții friend
operator .

Exemplul 12 . Supraîncărcarea operatorului + pentru adunarea a două numere complexe definite ca
obiecte ale clasei my_complex.

28

#include<iostream>
using namespace std;
class my_complex {
private:
double re;
double im;
public:
my_complex (double a, double b){ re = a; im = b;};
void afisare();
my_complex operator+(my_complex &op);
};
void my_complex :: afisare(){ cout << re << " +i* " << im << endl;};
my_complex my_complex :: operator+(my_complex &op){
my_complex t(0.0, 0.0);
t.re = this ->re + op.re;
t.im = this ->im + op.im;
return t;
};
int main(void){
my_complex t1(0.0,0.0), t2(0.0,0.0), *tt1, *tt2;
my_complex z1(2.0, 3.0), z2(7.0, 5.0);
z1.afisare(); z2.afisare();
z1 = z1 + z2; // testati si z1 = z1.operator+(z2);
z1.afisare();
tt1 = new my_complex (3.00, 11.00);
tt2 = new my_complex (5.0, 2.0);
t1 = *tt1; t1.afisare();
t2 = *tt2; t2.afisare();
t1 = t1 + t2; t1.afisare();
return 0;
}

Exemplul 13 . Extindem exemplul 12 pentru a suporta și alte operații aritmetice, precum și verificările
relaționale == și != definind tipul a_complex.

#include<iostream>
using name space std;
class a_complex {
private:
double re;
double im;
public:
a_complex();
a_complex(double);
a_complex(double, double);
a_complex operator+(const a_complex &op) const;
a_complex operator -(const a_Complex &op) const;
a_complex operator*(const a_Complex &op) const;

29
a_complex operator/(const a_complex &op) const;
bool operator==(const a_complex &op) const;
bool operator!=(const a_complex &op) const;
void a_afisare();
};
a_complex::a_compl ex() {re = im = 0.0;}; //implicit
a_complex::a_complex(double r) {re = r; im = 0.0;}; //nr reale
a_complex a_complex::operator+(const a_complex &u) const{
a_complex v(re + u.re, im + u.im);
return v;
}
a_complex a_complex::operator -(const a_complex &u) const{
a_complex v(re – u.re, im – u.im);
return v;
}
a_complex a_complex::operator*(const a_complex &u) const{
a_complex v( re * u.re – im * u.im, im * u.re + re * u.im);
return v;
}
a_complex a_complex::operator/(const a_complex &u) const{
double abs_pt = re * u.re + im * u.im;
a_complex v( ( re * u.re + im * u.im) / abs_pt,(im * u.re – re *
u.im ) / abs_pt );
return v;
}
bool a_complex::operator==(const a_complex &u) const{
return (re == u.re && im == u.im) ;
}
bool a_complex::operator!=(const a_complex &u) const{
return !(re == u.re && im == u.im) ;
}
void a_complex :: afisare(){ cout << re << " + " << im << "*i" <<
endl;};

2.7.2. Supraîncărcarea cu funcții operator de tip friend

Deoarece o funcție friend nu este membru al clasei, nu are disponibil un pointer de tip this. De aceea,
unei funcții supraîncărcate de tip friend operator i se vor transmite explicit operanzii. Nu putem
folosi funcțiile friend pentru supraîncărcarea operatorilor = [ ] ( ) și ->.

Sintaxa prototipului unei funcții friend operator este:
friend < tip rezultat > operator# ( <operand1>& , <operand2> &) ;,
precum în descrierea friend my_complex operator+ (my_complex &, my_complex &);

Exemplul 14. Supraîncărcare folosind fun cții friend . Este ilustrat transferul operandului prin valoare,
respectiv prin referință în cazul supraîncărcării operatorului unar – aplicat pentru numere complexe
(sau puncte din plan).

30
Transfer prin valoare Transfer prin referință
Cod C++ #include < iostream>
using namespace std;
class X{
double re, im;
public:
X(){re = im = 0.0;};
X (double r, double
i)
{re = r; im
=i;};
void afisare();
friend X operator –
(X);
};
X operator – (X z){
z.re = -z.re;
z.im = -z.im;
return z;}
void X :: afisare(){
cout << re << " +i*
"
<< im << endl;
}
int main (void) {
X z1(2.0, 3.0), z2;
z1.afisare();
z2 = – z1;
z2.afisare();
// z1 este
nemodificat
return 0;
} #include <iostream>
using name space std;
class X{
double re, im;
public:
X(){re = im = 0.0;};
X (double r, double i) {
re = r; im =i;};
void afisare();
friend X operator – (X &);
};
X operator – (X &z){
z.re = -z.re;
z.im = -z.im;
return z;}
void X :: afisare(){
cout << re << " +i* "
<< im << endl;
}
int main (void) {
X z1(2.0, 3.0);
z1.afisare();
– z1;
z1.afisare(); //z1 modificat
return 0;
}
Rezultatul
executării 2 +i* 3
-2 +i* -3 2 +i* 3
-2 +i* -3

2.8. Fluxuri de intrare -ieșire în C++

C++ definește propriul său sistem I/O orientat pe obiecte. Sistemul de fișiere C/C++ recunoaște două
tipuri de fluxuri:text și binar. Un flux de tip text este o secvență de caractere. Un flux binar este o
secvență de octeți. Un flux se asociază cu un anumit fișier printr -o operație de deschidere. Odată
deschis fișierul, este posibil schimbul de date între el și programul aflat în executare. Operațiile cu
fluxuri sunt posibile prin utilizarea claselor din iostream.h (inclusiv obiectel e cin din clasa istream ,
cout și cerr, ambele din clasa ostream ). Inserarea în flux se realizează folosind operatorul
(supraîncărcat) <<, iar extragerea din flux folosește operatorul >>.
Metodele pentru formatarea ieșirii sunt:
long setf (long indicator);//activarea indicatorilor de format
long unsetf(long indicator);
int width (int w);

31
int precision ( int p);
char fill (char ch);
Formatarea se mai poate realiza și cu ajutorul manipulatorilor: dec, hex, oct, endl,
flush, setfill (int), setpreci sion (int) și setw (int).

Operațiile cu fișiere se pot programa folosind clasele din fstream.h . Operațiile de intrare / ieșire cu
fișiere se fac prin intermediul claselor ifstream, ofstream și fstream. Constructorii acestor clase sunt:

ofstream::ofstream (const char * nume,ios::openmode= ios::out| ios::trunc);
ifstream::ifstream (const char * nume, ios::openmode= ios::in);
fstream::fstream (const char * nume, ios::openmode= ios::in | ios::out);

Modurile de deschidere a fișierelor sunt:
 ios::app – adauga la sfâr șitul fișierului;
 ios::ate – poziționează pointer -ul la sfârșitul fișierului, însă informațiile pot fi scrise
oriunde în cadrul fișierului;
 ios::trunc – este modul de deschidere implicit: vechiul conținut al fișierului este pierdut;
 ios::binary –deschide f ișierul în mod binar;
 ios::in – deschidere pentru operații de intrare;
 ios::out – deschidere pentru operații de ieșire.

Rezultatul operațiilor de intrare / ieșire poate fi testat prin intermediul a patru funcții membre:
 eof() – verifică dacă s -a ajuns la sfârșitul fișierului;
 bad() – verifică dacă s -a executat o operație invalidă;
 fail() – verifică dacă ultima operație a eșuat;
 good() – verifică dacă toate cele trei rezultate precedente sunt false.
Închiderea unui fișierse fa ce apelând metoda close().

Scrierea într -un fișier binar se face cu funcția:
ostream& write ( const char* s , size n);
Citirea dintr -un fișie rbinar se face cu funcția:
istream& read( const char* s , size n );
Obținerea poziției într -un fișier se face cu:
 pos_type ostream::tellp();
 pos_type istream::tellg();
Poziționarea într -un fișier se face cu:
 ostream&ostream::seekp(off_type offset,ios::seekdir pos);
 istream&istream::seekg(off_type offset,ios::seekdir pos);
unde pos poate avea una din următoarele valori :
 ios::beg – specifică poziția de la începutul fluxului;
 ios::end – specifică poziția de la sfârșitul fluxului;
 ios::cur – specifică poziția curentă a fluxului;

Exemplul 15. Citirea dintr -un fișier text și scriere în alt fișier text după efectuarea unei transformări.

#include<iostream>
#include<fstream>
#include <string.h>
using namespace std;

32
int main(void){
int k=0;
int x[100];
int i=0;
fstream f("fis.in",ios:: in);
fstream g("fis.out",ios::out);
while(!feof()&& k<100){f >> {x[k]; k++;}
// primele 100 de numerele (daca sunt atatea) din fis.in se depun
in tabloul x
f.close();
for (i=0; i<k; i++) x[i] = x[i]+1; // se inlocuieste cu
succesorul
for (i=0; i<k; i++) g << x[i];
g.close();
fstream g("fis.out",ios::app);
g << 666; //se adauga 666 la final.
g.close();
return 0;
}

2.9. Tratarea excepțiilor

Prin programare defensivă se vor crea programe robuste care tratează erorile (excepțiile) care apar în
timpul executării. Pentru aceasta în C++ mai sunt disponibile cuvintele cheie try, throw și catch .
Programul plasează instrucțiunea throw în cadrul blocu lui try-catch . Forma generalizată a blocului
care captează și tratează erorile este:
try{
//blocul try
//if(eroare) throw valoare_excepție;
}catch (Tip_excepție Nume_variabilă ){ //Prelucrarea excepției
}

Exemplul 16 . Programul verifică dacă s -a depasit capacitatea rablului x.

#include<iostream>
using namespace std;
int main () {
char x[10];
try{
for (int n=0; n<=10; n++){
if (n>9) throw "Depasire";
x[n]='z';
}
} catch (char * str){
cout << "Exceptie: " << str << endl;
}
return 0;
}

33

2.10. Programare generică în C++. Biblioteca standard de șabloane.

Programarea generică este un stil de programare a calculatoarelor în care algoritmii sunt specificați în
termeni de tipuri ce urmează a fi precizate în momentul apelului (utilizării). Astfel, o funcție generică
definește un set general de operații care vor fi aplicate unor tipuri de date diferite. În C++ există suport
evoluat pentru programare generică, sub forma șabloanelor (templates). O funcție template este o
funcție șablon, având unul sau mai mulți parametri formali de un tip generic. La utilizare, compilatorul
generează funcții propriu -zise, înlocuind tipul generic cu un tip concret. Tipul concret poate fi orice tip
fundamental, derivat sau clasă predefinită. Sintaxa la specificare este:
template <class Nume_tip_generic_1 [,… class Nume_tip_generic_n]>

Sintaxa la utilizare este:
Nume șablon<Expresie_1[, …,Expresie_n]>;
În scrierile de mai sus, caracterele < și > fac parte din sintaxa obligatorie. Următoarea secvență
specifică funcția șablon max:
template <class T> T max(T x, T y) { return (x>y) ? x : y; }

O clasă template definește un șablon pe baza căruia se pot genera clase propriu -zise. Din acest motiv,
o cla să template se mai numește și clasă generică , clasă generator sau metaclasă. Astfel, o clasă
template devine o clasă de clase, reprezentând cel mai înalt nivel de abstractizare admis de
programarea orientată pe obiecte.

Sintaxa la specificarea clasei est e:
template <class T> class nume_clasă { …………….};
Sintaxa la implementarea funcțiilor membre ale unei clase template este:
template <class T> Tip returnat nume_clasa <T>::nume_funcție(…) {
………};

Exemplul 17 . Tablouri generice.

#include <iostream>
using namespace std;
const int DIM = 100;
//***************************************
//Definire clasa tablou generic
template <class A> class genTAB {
A a[DIM];
public:
genTAB(); //constructor
A &operator[](int k); //supraincarcare operato r [ ]
};
//implementare constructor clasă generica
template <class A> genTAB<A>::genTAB() {
int k;
for(k=0; k<DIM; k++) a[k]=k*k;
};
//implementare supraincarcare operator []
template <class A> A &genTAB<A>::operator[](int k) {

34
if(k<0 ||k>= DIM){
cout << "Index in afara domeniului admis.";
exit(1);
};
return a[k];
};
//testare
int main(void) {
genTAB<int> intA;
genTAB<double> doubleA;
int k;
cout<<"Tabloul patratelor numerelor" << endl;
for(k=0; k<DIM; k++) intA[k]=k*k;
for(k=0; k<DIM; k++) cout << k << intA[k] << endl;
cout << "Tabloul optimilor indicilor" << endl;
for(k=0; k<DIM; k++) doubleA[k]=(double)k/8;
for(k=0; k< DIM; k++) cout << k << doubleA[k] << endl;
intA[DIM]=DIM;
};

Biblioteca standard C++ are trei mari componente: biblioteca de funcții C, cea de fluxuri de intrare /
ieșire și Standard Template Library (STL) (Biblioteca standard de șabloane). STL are la bază
polimorfismul parametric și trei concepte centrale: containeri, iteratori și algoritmi.

Un container este un obiect care stochează o colecție de alte obiecte (elementele sale). Un container
poate fi definit ca fiind o colecție de date car e suportă cel puțin următoarele operații: adăugarea unui
element în container; ștergerea unui element din container; returnarea numărului de elemente
din container (dimensiunea containerului); permite accesul la obiectele stocate (de obicei folosind
iterat ori) și permite căutarea unui obiect în container.

Containerii sunt implemetați drept clase șablon și se referă la cele mai importante structuri de date:
tablouri dinamice (vector), cozi (queue), stive (stack), heap -uri (priority_queue), liste înlănțuite (list),
mulțimi (set), tablouri asociative (map), etc.

Iteratorii sunt o generalizare a referințelor și anume ca obiecte care referă alte obiecte. Iteratorii sunt
des utilizați pentru a parcurge un container de obiecte. Iteratorul se mai numește și cursor . Iteratorii
pot fi incrementați cu ++, dereferențiați cu * și comparați cu !=. Containerele pot genera iteratori cu
funcțiile begin() și end().

Iteratorii pot fi împărțiți în categorii de iteratori, în funcție de operațiile care se pot efectua asupra lo r.
(iteratori de intrare, iteratori de ieșire, iteratori de avans, iteratori bidirecționali și iteratori cu acces
direct).

Algoritmii STL sunt generici deoarece se pot aplica la o mare varietate de structuri de date. Antetul
<algorithm> definește o colecț ie de funcții special concepute pentru a fi utilizate pe subșiruri de
elemente.

Exemplul 18. Ordonarea descrescătoare a unui șir de numere folosind elemente STL.

35
#include<iostream>
#include<algorithm>
using namespace std;
int main(){
int x[] = {10, 17, 20, -5, 14, 78, 11, 61, 25} ;
sort( x, x+9, greater<int>());
for (int k=0 ; k<9 ; k++) cout << x[k] << " ";
return 0;
}

Tematica asupra STL va fi extinsă în altă lucrare. Cititorii interesați pot consulta lucrarea [5].

1. Implemen tarea algoritmilor în C++: aplicații cu numere, caractere, șiruri de caractere, tablouri,
liste, grafuri, dicționare.

Exemplul 19 [Aplicatie cu numere]. Se citesc L fracții dintr -un fișier text numit fractii.in . Să se
afișeze numai fracțiile reductibile ordonate descrescător. Modificați implementarea pentru a înlocui
struct cu class .

#include <iostream>
#include <fstream.h>
using namespace std;
struct fract {int p,q;};
ifstream fin("fractii.in");
void read(fract &f){fin >> f.p >> f.q;}
void print(fract f){cout << f.p << "/" << f.q << "#";}
void sortSCH(int m,fract *x){ //sortare prin interschimbare
int k, ok; fract temp;
do{
ok=1; //sirul este sortat
for(k=0;k<m;k++)
if (x[k].p * x[k+1].q < x[k].q*x[k+1].p){
temp = x[k]; x[k] = x[k+1];
x[k+1]=temp; ok=0;
}
}while (!OK);
}
int cmmdc(int a,int b) {while(a!=b) if (a>b) a=a -b; else b=b -a;
return a;}
int main(void) {int k, L; fract x[100]; //presupunem ca nu exista
mai mult de 100
f >> L;
for (k=0; k<L; k++) read(x[k]);
sortSCH(L,x);
for (k=0;k<L;k++) if(cmmdc(x[k].p,x[k].q)!=1) print (x[k]);
//filtrarea fractiilor
f.close();
return 0;
}

36

Exemplul 20 [Aplicație cu caractere și șiruri de caractere]. șirul de caractere este o colecție de
caractere. Există două tipuri de șiruri de caractere utilizate în mod obișnuit în limbajul de programare
C ++: obiecte ale clasei String (în C++), respectiv tablouri de caractere conform specificațiilor
string.h din limbajul C. Accesul în C++ la caracterul de pe poziția k din șirul text este
text[k]. Numărul de caractere ale obiectului text este text.size .

#include <iostream>
using namespace std;
int main(void){
// Declararea unui obiect string
string text;
cout << "Introduceti textul: ";
getline(cin, text);
cout << "A fost introdus: " << text << endl;
return 0;
}

Exemplul 21 [Aplicație cu tablouri bidimensionale]. Scrieți un program pentru a proiecta o clasă
pentru lucrul cu matrice. Clasa ar trebui să aibă funcționalitatea de a insera (upload) și de a prelua
(download) elementele matricei.

#include<iostream.h>
class MAT {
int **p; //pointer – alocare dinamica a spatiului
int dim1, dim2;
public:
MAT(int x, int y);
void uplo ad(int i, int j, int val) {
p[i][j]=value;
}
int & download(int i, int j) {
return p[i][j];
}
};
MAT ::MAT(int m, int n) {
dim1 = m;
dim2 = n;
p = new int *[dim1];
for(int k = 0; k < dim1; k++)
p[k] = new int[dim2];
}
int main() {
int m, n;
cout<<"Introduceti dimensiunile matricei";
cin >> m >> n;
MAT A(m,n);
Cout << "Dati elementele matricei pe linii:";
int i,j,val;

37
for(i=0; i<m; i++)
for(j=0;j<n;j++){
cin >> val;
A.upload(i,j,value);
}
Cout << " \n";
Cout << A.download(2,3);
return 0;
}

Exemplul 22 [Aplicație cu liste]. Se va crea clasă Lista pentru manipularea listelor înlănțuite și se va
folosi în funcția main..

#include <iostream>
#include <cstlib>
using namespace std;

class Nod{
public:
Nod* leg;
int data;
};
class Lista{
public:
int lung;
Nod* h; //capul listei
Lista();
~Lista(); //destructor
void adauga(int data);
void afisare();
};
Lista::Lista(){
this->lung = 0;
this->h = NULL;
}
Lista::~Lista(){
cout << "Se elibereaza memoria";} //Completati cod ul
void Lista::adauga(int data){
Nod* nod = new Nod();
nod->data = data;
nod->leg = this ->h;
this->h = nod;
this->lung++;
}
void Lista::afisare(){
Nod* p = this ->h;
int k = 1;
while(p){
cout << i << ": " << p ->data << endl;

38
p = p->leg;
k++;
}
}

int main(){
Lista* L = new Lista();
for (int k = 0; k < 100; ++k){
list->adauga(rand() % 6);//numere generate aleator
}
L->afisare();
cout << "Numarul de elemente din lista: " <<
L->lung << endl;
delete L; // Ce spatiu se elibereaza?
return 0;
}

Exemplul 23 [Aplicație cu grafuri]. Următorul program verifică conexitatea unui graf neorientat
folosind căutarea în adâncime (eng. DFS – depth first search)

#include <iostream>
#include <list> // din STL
#include <stack> // din STL
using namespace std;
class Graf{
private:
int V; // varfuri
list<int> *lad; //lista de adiacenta
void DFS(int v, bool vizitat[]);
public:
Graph(int V){ // constructor
this->V = V;
lad = new list<int>[V];
}
~Graph(){ //destructor
delete [] lad;
}
void muchie(int v, int w);
bool esteconex();
Graf Transpus();
};
void Graf::DFS(int v, bool vizitat[]){
vizitat[v] = true;
list<int>::iterator k;
for (k = lad[v].begin(); k != lad[v].end(); ++k)
if (!vizitat[*k]) DFS(*k, vizitat);
}
Graf Graf::Transpus(){
Graf g(V);

39
for (int v = 0; v < V; v++){
list<int>::iterator k;
for(k = lad[v].begin(); k != lad[v].end();
++k)g.lad[*k].push_back(v);
}
return g;
}
void Graf::muchie(int v, int w){
lad[v].push_back(w);
lad[w].push_back(v);
}
bool Graf::esteconex(){
bool vizitat[V];
for (int v = 0; v < V; v++) vizitat[v ] = false;
DFS(0, vizitat);
for (int v = 0; v < V; v++) if (vizitat[v] == false) return false;
Graf gt = Transpus();
for(int v = 0; v < V; v++) vizitat[v] = false;
gt.DFS(0, vizitat);
for (int v = 0; v < V; v++) if (vizitat[v] == false) return false;
return true;
}
int main(void){
Graph g(5);
g.muchie(0, 1); g.muchie(1, 2); g.muchie(2, 3); g.muchie(3, 0);
g.muchie(2, 4);
g.muchie(4, 2);
if (g.esteconex()) cout <<"Graful este conex" << endl; else
cout << "Graful nu este conex" << endl;
return 0; // adaptare dupa [9]
}

Exemplul 24 [Aplicație cu dicționare]. Dictionarul este organizat sub forma unui arbore de cautare

#include<iostream>
#include<stdlib.h>
using namespace std;
#define maxim 100
typedef struct lista{
int data;
struct lista *leg;
} tipnod;
tipnod *pointer[maxim], *radacina[maxim], *temporar[maxim];
class Dictionar{
public:
int index;
Dictionar(); //constructor
void inserare(int);
void cautare(int);

40
void stergere(int);
};
Dictiona r::Dictionar(){
index = -1;
for (int k = 0; k < maxim; k++){
radacina[k] = NULL;
pointer[k] = NULL;
temporar[k] = NULL;
}
}
void Dictionar::inserare(int cheie){
index = int(cheie % maxim);
pointer[index] = (tipnod*) malloc(sizeof(tipnod));
pointer[index] ->data = cheie;
if (radacina[index] == NULL){
radacina[index] = pointer[index];
radacina[index] ->leg = NULL;
temporar[index] = pointer[index];
}
else{
temporar[index] = radacina[index];
while (temporar[index] ->leg != NULL)
temporar[index] = temporar[index] ->leg;
temporar[index] ->leg = pointer[index];
}
}
void Dictionar::cautare(int cheie) {
int ok = 0;
index = int(cheie % maxim);
temporar[index] = radacina[index];
while (temporar[index] != NULL){
if (temporar[index] ->data == cheie){
cout << " \nCheie gasita"; ok = 1; break;}
else temporar[index] = temporar[index] ->leg;
}
if (!ok) cout << " \nCheia nu a fost gasita";
}
void Dictionar::stergere(int cheie){
index = int(cheie % maxim);
temporar[index] = radacina[index];
while (temporar[index] ->data != cheie && temporar[index] !=
NULL){
pointer[index] = tem porar[index];
temporar[index] = temporar[index] ->leg;
}
pointer[index] ->leg = temporar[index] ->leg;
cout << " \n" << "A fost sters: " << temporar[index] ->data;
temporar[index] ->data = -1;
temporar[index] = NULL;

41
free(temporar [index]);
}
int main(void){
int opt, n, cheie; char c;
Dictionar d;
do {//afisare meniu
cout << " \nMENIU:\n1.Creare";
cout << " \n2.Cautare valoare \n3.Stergere valoare";
cout << " \nOptiunea ta este:"; cin >> opt;
switch (opt){
case 1:
cout << " \nNumarul de elemente de inserat:";
cin >> n;
cout << " \nElementele de inserat sunt:";
for (int k = 0; k < n; k++){
cin >> cheie; d.inserare(cheie);}
break;
case 2:
cout << " \nSe va cauta elementul:"; cin >>
cheie;
d.cautare(cheie);
break;
case 3:
cout << " \nSe va sterge elementul:";
cin >> cheie;
d.stergere(cheie);
break;
default:
cout << " \nOptiune incorecta.";
break;
}
cout <<
"\nApasa D sau d pentru a continua. Altfel, programul
se opreste.";
cin >> c;
} while (c == 'd' || c == 'D');
}

3. Antrenamente C++

3.1. Noțiuni fundamentale

Recapitulează fundamentele programării orientate pe obiecte. Acum știi ….

1. Ce este un obiect?

2. Care sunt principalele avantaje ale folosirii obiectelor?

42

3. Cum se creează un obiect?

4. Avantajele și dezavantajele utilizării funcțiilor inline.

5. Care sunt restricțiile impuse funcțiilor inline?

6. Ce este moștenirea?

7. Ce este moștenirea multiplă?

8. Cum se folosesc două variabile cu același nume într -un program C++?

9. Ce sunt funcțiile constructor și destructor?

10. Ce tip de dată poate returna un constructor?

11. Cum se apelează constructorii?

12. Ce tipuri de constructori sunt permiși în C++?

13. Ce sunt constructorii de copiere?

14. Cum se atribuie valoar ea unui obiect, altui obiect?

15. Ce este o listă de inițializare?

16. Cum se folosesc funcțiile constructor în cadrul moștenirii?

17. Cum acționează funcțiile destructor în cadrul moștenirii?

18. Câți destructori poate avea o clasă?

19. Ce este un membru protejat și la ce folosește?

20. Variabilele membru private pot fi accesate și în afara clasei respective?

21. Regulile privind accesul la membrii claselor în cazul derivării claselor.

22. Cum se accesează un membru al unui obiect accesat direct? Dar un membru al unui obiect
accesat indirect, prin intermediul unui pointer?

23. Ce înseamnă supraîncărcarea funcțiilor?

24. O clasă poate avea mai mulți constructori? Dacă da, atunci cum știe compilatorul să facă
diferențierea între aceștia?

25. Ce înseamnă redefinirea metodelor în cadrul cl aselor specializate?

43
26. Ordinea claselor de bază, moștenite de o clasă, este importantă?

27. Ordinea de apel a constructorilor claselor derivate este importantă?

28. Cum se utilizeazaă parametrii prestabiliți?

29. Cum se fac conversiile între clasa de bază și clasa d erivată?

30. Ce reprezintă supraîncărcarea operatorilor?

31. Cum se utilizează un operator supraîncărcat?

32. Care sunt operatorii C++ supraîncărcabili?

33. Ce operatori C++ nu se pot supraîncărca?

34. Cum se creează un operator supraîncărcat folosind o funcție membru a clasei?

35. Cum se creează un operator supraîncărcat folosind o funcție friend a clasei.

36. Cum se alocă memorie în programele C++?

37. Cum se eliberează memnoria alocată anterior?

38. Ce este polimorfismul și la ce folosește?

39. Ce tipuri de polimorfism suportă limbajul C++?

40. Ce este o funcție virtuală și cum se definește?

41. Pot fi declarați constructorii ca fiind virtuali? Dar destructorii?

42. Ce este o funcție virtuală pură?

43. Ce este o clasă abstractă?

44. Ce este o interfață în C++?

45. Ce este legarea internă și externă?

46. Care este diferența dintre clase ( class ) și structuri ( struct )?

47. Ce trebuie specificat la deschiderea unui fișier?

48. Ce clase sunt definite în fișierul fstream.h?

49. Care sunt modurile de deschidere a fișierelor?

50. Cum se deschide un fișier?

44

51. Cum se închide un fișier?

52. Cum se realizează operațiile de intrare / ieșire după deschiderea fișierului?

53. Cum poate fi testat rezultatul operațiilor de intrare / ieșire?

54. Cum se poziționează pointerul (indicatorul) de fișier pentru operațiile de intrare / ieșire?

55. Ce sunt cazurile de excepție?

56. Cum se realizează tratarea excepțiilor în C++?

57. Ce este un șablon și la ce folosește?

58. Cum se creează un șablon de funcție?

59. Cum se creează un șablon de clasă?

60. Cum se creează obiecte folosind un șablon de clasă?

3.2. Studii de caz

Fără a utiliza un compilator sau mediu de programare analizați următoarele secvențe C++ (studii de
caz) și rezolvați corect cerințele de mai jos.

Studiul de caz nr. 1. Analizați codul următor (constructor, destructor, excepții, obiecte). Ca re este
rezultatul afișat pe ecran în urma executării programului?

#include <iostream>
class A{
public:
int m_n;
A(int n): m_n(n){if(0 == n) {throw "Examen";}}
~A(){std::cout << m_n;}
};
int main(){
try {A a(1); A b(3); A c(2);} catch( … ) {std::cout < < 3;}
return 0;
}

Studiul de caz nr. 2. Analizați codul următor (constructor, șabloane, obiecte). Care este rezultatul
afișat pe ecran în urma executării programului?

#include <iostream>
template <unsigned N>
class A{
public: A(){std::cout << N;}
private: A<N-1> m_a;

45
};
template<> class A<0>{
public: A() {std::cout << 'A';}
};
int main(){A<4>(); return 0;}

Studiul de caz nr. 3. Analizați codul următor (clase, constructor, destructor, obiecte). Care este
rezultatul afișat pe ecran în urma executării programului?

#include <iostream>
class A{
public:
A(int n=2) : m_i(n){}
~A(){std::cout << m_i;}
protected: int m_i;
};
class B : public A{
public: B(int n): m_x(m_i+1), m_a(n){}
public: ~B(){std::cout << m_i; –m_i;}
private: A m_x; A m_a;
};
int main(){ B b(5); return 0;}

Studiul de caz nr. 4. Analizați codul următor (clase constructori , c#ampuri statice, obiecte). Care este
rezultatul afișat pe ecran în urma executării programului?

#include <iostream>
class A {
public: A(int n=0) : m_i(n){ std::cout << m_i;}
protected: int m_i;
};
class B : public A {
public: B(int n):m_j(n), m_a( –m_j), m_b(){std::cout << m_j;}
private: int m_j; A m_a; A m_b;
static A m_c;
};
A B::m_c(3);
int main(){B b(2); return 0;}

Studiul de caz nr. 5. Analizați codul următor (clase, constructori , operatorii new și delete, obiecte).
Care este rezultatul afișat pe ecran în urma executării programului?

#include <iostream>
class A{
public: A(int n=0):m_i(n){std::cout << m_i; ++m_i;}
protected: int m_i;
};
class B: public A{
public: B(int n=5):m_a(new A[2]), m_x(++m_i){std::cout << m_i;}

46
~B(){delete [] m_a;}
private: A m_x;
A* m_a;
};
int main(){B b ; return 0;}

Studiul de caz nr. 6. Analizați codul următor (clase, constructori, destructori, excepții, obiecte). Care
este rezultatul afișat pe ecran în urma executării programului?

#include <iostream>
class A{
public: A(int n):m_n(n){std::cout << m_n;}
~A() { std::cout << m_n;}
private: int m_n;
};
int f(int n){ if (1==n) {throw 0;} A l(n); return f(n -1)*n/(n-
1); }
int main(){
try {int r = f(3); A a(r);}
catch (int e){std::cout << e << std::endl;}
return 0;
}

Studiul de caz nr. 7. Analiz ați codul următor (clase, constructori, destructori, gestiunea dinamică a
obiectelor, virtualizare). Care este rezultatul afișat pe ecran în urma executării programului?

#include <iostream>
class A{
public:
virtual ~A(){f();}
public:
virtual v oid f() const{ std::cout << 1;}
};
class B: public A {
public:
~B(){f();}
private:
virtual void f() const {std::cout << 2;}
};
int main(){
A* a = new B;
delete a;
std::cout << std::endl;
return 0;
}

Studiul de caz nr. 8. Analizați codul următor (clase, constructori, destructori, gestiunea dinamică a
obiectelor, exceptii). Care este rezultatul afișat pe ecran în urma executării programului?

47
#include <iostream>
class A {
public:
A(int n){if (0==n){throw "Examen";}}
};
int main(){
A *p0 = 0, *p1 = 0, *p2 = 0;
try{
p1 = new A(1);
p0 = new A(0);
p2 = new A(2);
} catch( … ){std::cout << 3;}// orice exceptie
std::cout << (( 0 != p1)? 1:0);
std::cout << (( 0 != p0)? 1:0);
std::cout << (( 0 != p2)? 1:0) << std::endl;
delete p1;
delete p0;
delete p2;
return 0;
}

Studiul de caz nr. 9. Analizați codul următor (clase, constructori, destructori, gestiunea dinamică a
obiectelor, exceptii). Care este rezultatul afișat pe ecran în urm a executării programului?

#include <iostream>
class C{
public:
C(int i): i(i){std::cout << i;}
~C(){std::cout << i+5;}
private: int i;
};
int main(){
C(2);
const C& c = C(1); return 0;
}

Studiul de caz nr. 10. Analizați codul următor (clase, constructori, destructori, referințe, virtualizare).
Care este rezultatul afișat pe ecran în urma executării programului?

#include <iostream>
class B{
public:
virtual int shift(int n = 2) const { return n << 2;}
};
class D: public B {
public:
int shift(int n = 3) const{ return n <<3;}
};
int main(){

48
const D d;
const B* b = &d;
std::cout << b -> shift(); return 0;
}

Studiul de caz nr. 11. Analizați codul următor (clase, constructori, destructori, referințe, virtualizare).
Care este rezultatul afișat pe ecran în urma executării programului?

#include <iostream>
class A{
public:
virtual void f(int n){std::cout << n << 1;}
virtual ~A(){}
void f(int n) const { std::cout << n;}
};
class B: public A{
public: void f(int n){std::cout << (n << 1);}
void f(int n) const {std::cout << n+1;}
};
int main(){
const A a;
B b;
A& c = b;
const A* d = &b;
a.f(2);
b.f(2);
c.f(1);
d->f(1);
return 0;
}

Studiul de caz nr. 12. Analizați codul următor (clase, constructori, destructori, alocare / eliberare,
virtualizare). Care este rezultatul afișat pe ecran în urma executării programului?

#include <iostream>
class A{
public:
A(){f();}
virtual ~A(){}
public:
virtual void f() const{ std::cout << 1;}
};
class B: public A {
public:
B(){f();}
private:
virtual void f() const {std::cout << 2;}
};
int main(){

49
A* a = new B;
delete a;
std::cout << std::endl;
return 0;
}

Studiul de caz nr. 13. Analizați codul următor (cu ierarhie de clase). Care este rezultatul afișat pe
ecran în urma executării programului?

#include <iostream>
class E{
typedef int INT;
};
class D : public E{};
class C : public D{};

int main(){
std :: cout << sizeof(E) << " " << sizeof(D) << " " <<
sizeof(C) <<std :: endl;
return 0;
}

Studiul de caz nr. 14. Analizați codul următor (cu ierarhie de clase și virtualizare). Care este
rezultatul afișat pe ecran în urma executării programu lui?

class B{
public:
B(){P();}
~B(){P();}
void Q() {P();}
virtual void P(int x = 0x10){ std::cout << "U::P:x = " <<
x << std::endl;}
};
class C : public B {
public:
C(){}
virtual void P(int x = 0x100) { std::co ut << "V::P:x = "
<< x << std::endl;}
};
int main(){
B *b = new C();
b -> Q();
delete b;
return 0;
}

Studiul de caz nr. 15. Analizați codul următor (cu clase și virtualizare). Care este rezultatul afișat pe
ecran în urma executării programului?

50
#include <iostream>
class A{
public:
~A(){std :: cout << "A :: ~A()" << std :: endl;}
};
class B{
public:
~B(){std :: cout << "B :: ~B()" << std :: endl;}
};
class S{
A a;
B b;
public:
S(): b(), a() {}
virtual ~S() {std :: cout << "S :: ~S()" << std :: endl;}
};
int main (void) {S s; return 0;}

Studiul de caz nr. 16. Analizați codul următor (cu moștenire multiplă și virtualizare). Care este
rezultatul afișat pe ecran în urma executării programului?

#include <iostream>
class A{
public:
~A(){std :: cout << "A :: ~A()" << std :: endl;}
};
class B{
public:
~B(){std :: cout << "B :: ~B()" << std :: endl;}
};
class S : public A, public B {
public:
virtual ~S() {std :: cout << "S :: ~S()" << std :: endl;}
};
int main (void) {S* s = new S(); delete s; return 0;}

Studiul de caz nr. 17. Analizați codul următor (cu ierarhie de clase și virtualizare). Care este
rezultatul afișat pe ecran în urma executării programului?

#include <i ostream>
class F{
public:
F() {std :: cout << "F::F()" << std :: endl;}
virtual ~F(){std :: cout << "F::~F()" << std :: endl;}
};
class A: public F{
int x, y;
public:
A(){std :: cout << "A::A()" << std :: endl;}

51
virtual ~A(){std :: cout << "A::~A()" << std :: endl;}
};
class B: public A{
int z;
public:
B(int z = 0) {std :: cout << "B::B()" << std :: endl;}
virtual ~B(){std :: cout << "B::~B()" << std :: endl;}
};
int main (void) {
F * f[10]; int n = 0;
f[n++] = new B();
f[n++] = new A();
for (int i=0; i<n; i++) delete f[i];
return 0;
}

Studiul de caz nr. 18. Analizați codul următor (tipuri de constructori). Care este rezultatul afișat pe
ecran în urma executării progra mului?

#include <iostream>
class A{
public:
A(){ std :: cout <<"A::A()" << std :: endl;}
A(A &a){std :: cout <<"A::A(A&)" << std :: endl;}
~A(){std :: cout <<"A::~A()" << std :: endl;}
};
int main (void) {
A a;
A x = a;
A y(x);
return 0;
}

Studiul de caz nr. 19. Analizați codul următor (supraîncărcare operatori ++ și –). Care este rezultatul
afișat pe ecran în urma executării programului?

#include <iostream>
class A{
public:
A(float pa): a(pa){}
float Get(){return a;}
A operator++(){a++; return *this;}
A operator –(){a–; return *this;}
private:
float a;
};
int main (void) {
A a(1.0);
std :: cout << (++ (++ a)).Get() << std :: endl;

52
std :: cout <<a.Get() << std :: endl;
std :: cout << ( – (– a)).Get() << std :: endl;
std :: cout <<a.Get() << std :: endl;
return 0;
}

Studiul de caz nr. 20. Analizați codul următor (lucru cu vectori STL). Simulați executarea
programului pentru primii 10 termeni ai șirului lui Fibonacci: 0,1, 1, 2, 3, 5, 8, 13, 21, 34.

#include <iostream>
#include <vector>
#include <string>
#include <cstdlib>
using namespace std;
int main(){
vector<int> v;
vector<int>::iterator k;
int opt, x;
while (1){
cout<<"\n––––––– "<<endl;
cout<<"Vector STL"<<endl;
cout<<"\n––––––– "<<endl;
cout<<"1.Insert(e)"<<endl;
cout<<"2.Delete(e)"<<endl;
cout<<"3.Dim(v)"<<endl;
cout<<"4.Display(v)"<<endl;
cout<<"5.Iterate(v)"<<endl;
cout<<"6.Clear(v)"<<endl;
cout<<"7.Exit"<<endl;
cout<<"Alege =: ";
cin>>opt;
switch(opt) {
case 1:
cout<<"Val = "; cin>>x; v.push_back(x); break;
case 2:
cout<<"Del:"<<endl; v.pop_back(); break;
case 3:
cout<<"Dim: "; cout<<v.size()<<endl; break;
case 4:
cout<<"Display(v): ";
for (int index = 0; index < v.size(); index++){
cout<<v[index]<<" " ;}
cout<<endl;
break;
case 5:
cout<<"Iterare: ";
for (k = v.begin(); k != v.end(); k++){ cout<<*k<<"
"; }
cout<<endl;

53
break;
case 6:
v.clear(); cout<<"Curat !"<<endl; break;
case 7:
exit(1); break;
default:
cout<<"Alegere gresita"<<endl;
}
}
return 0;
}

3.3. Probleme propuse spre rezolvare

Folosiți noțiunile de bază POO și cunoștințe fundamentale de algoritmică și programare, rezolvați
problemele de mai jos.

Problema 1 [Bară Orizontală] Definiți o clasă BaraO al cărei constructor tipărește la consolă, pe un
r#and nou , +––-+ (șirul începe cu semnul „plus”, apoi sunt semne „minus”, iar ultimul semn
din șir este un „plus”). Numărul de semne minus se transmite constructorului ca argument.

Problema 2 [Bară verticală] Definiți o clasă BarV al cărei constructor tipărește un anumit număr de
semne „|” în felul următor:
|
|
|
Numărul de semne “| “ se dă ca argument constructorului.

Problema 3 [Numere raționale] Creați un tip de date definit de utilizator(o clasă) pentru numere
raționale. Numele tipului va fi Fractie. Definiți pentru acest tip de date operațiile de adunare, scădere,
înmulțire, împărțire, i nversare, egalitate

Problema 4 [Timpul -1] Creați un tip de date definit de utilizator (Timp) pentru reprezentarea timpului
sub forma (ora:minutul:secunda.sutimea). Pentru acest tip de date implemetați operația de adunare a
doi timpi. Se vor utiliza doi co nstructori ai obiectelor de tip Timp ( două modalități de inițializare a
unui obiect de tip Timp):
Timp t1(3, 15, 01, 02);
Timp t2(“3.15.01.02”);
Implementați o metodă care să determine timpul scurs de la o anumită ora până la ora curentă. “Ora
curentă” este cea dată de ceasul intern al calculatorului.

Problema 5 [Timpul -2] Creați un tip de date definit de utilizator (Timp) pentru reprezentarea timpului
sub forma (ora:minutul:secunda.sutimea). Pentru acest tip de date implementați o metodă care să
determ ine timpul scurs de la o anumită ora până la ora curentă. “Ora curentă” este cea dată de ceasul
intern al calculatorului. Se vor utiliza doi constructori ai obiectelor de tip Timp ( două modalități de
inițializare a unui obiect de tip Timp):
Timp t1(3, 15, 01, 02);
Timp t2(“3.15.01.02”);

54
Problema 6 [Ziua de azi] Creați un tip de date definit de utilizator (Data) pentru reprezentarea datei
sub forma (zi.luna.an). Pentru acest tip de date implementați o operație pentru determinarea numărului
de zile dintre d ouă date. Se vor utiliza două modalități (constructori) de inițializare a unui obiect de tip
Data:
Date d1(25, 1, 1961);
Date d2(“2.92.1992”);

Problema 7 [Vârsta unei persoane] Creați un tip de date definit de utilizator (Data) pentru
reprezentarea datei sub forma (zi.luna.an). Se vor utiliza două modalități (constructori) de inițializare a
unui obiect de tip Data:
Date d1(25, 1, 1961);
Date d2(“8.6.1884”);
Pentru acest tip de date implementați o metodă care să determine vârsta unei persoane în ani, luni ș i
zile. Data curentă va fi cea dată de ceasul intern al sistemului de calcul.

Problema 8 [Vectori în plan] Un vector în plan se poate specifica printr -o pereche de numere reale. Să
se implementeaze o clasa Vector2 pentru lucrul cu vectori în plan care să suporte următorii operatori
(supraîncărcați): adunare, scădere, înmulțire cu un scalar și produsul scalar a doi vectori. Să se scrie un
program demonstrativ care utilizează clasa Vector2.

Problema 9 [Matrice] Sa se scrie un program care implementează clas a Matrice și suportă operațiile:
creare, afișare, adunare, diferență, transpunere. Programul demonstrativ citește de la tastatură două
matrice, calculeaza suma, diferenta și transpusa acestora, cu afișarea rezultatului obținut după fiecare
operație executa tă.

Problema 10 [Greedy – Conexitate] Pentru o clasa Graf se cere sa implementați o metodă pentru
determinarea componentei conexe a unui nod dat.

Problema 11 [Backtracking – Dame] Se cere un program C++ pentru rezolvarea problemei celor n
dame, n>3.

Problema 12 [Backtracking – Permutări] Se cere un program C++ pentru generarea tuturor
permutărilor unei mulțimi cu n elemente, n>1.

Problema 13 [Backtracking – Submulțimi] Se cere să se determine toate submulțimile cu m elemente
ale unei mulțimi cu n elemente, unde 0 < m <= n.

Problema 14 [Greedy – Arbore parțial de cost minim] Se cere un program C++ cu clase pentru
determinarea arborelui parția l de cost minim al unui graf neorientat ponderat.

Problema 15 [Programare dinamică – Distanțe minime] Se cere un program C++ cu clase pentru
determinarea matricei distanțelor minime dintre oricare două noduri.

Problema 16 [Divide et impera – Sortare pri n interclasare] Se cere implementarea unei clase tablou de
numere reale pentru care să poată fi aplicată sortarea prin interclasare (merge sort).

Problema 17 [Divide et impera – Sortare rapidă ] Se cere implementarea unei clase tablou de numere
reale pent ru care să poată fi aplicată metoda sortării rapide (Quick sort).

55
Problema 18 [Căutare binară] Se cere implementarea unei clase tablou de numere reale pentru care să
poată fi aplicată metoda căutării binare.

Problema 19 [Sortare prin metoda bulelor] Se c ere implementarea unei clase tablou de numere reale
pentru care să poată fi sortat prin metoda bulelor (bubble sort).

Problema 20 [Sortare prin metoda selecției] Se cere implementarea unei clase tablou de numere reale
pentru care să poată fi sortat prin m etoda selecției.

Problema 21 [Sortare prin metoda inserției] Se cere implementarea unei clase tablou de numere reale
pentru care să poată fi sortat prin metoda inserției.

Problema 22 [Explorarea arborilor în lătime] Se cere implementarea unei clase pentr u stocarea
arborilor oarecare care să suporte explorarea în lățime a obiectelor de tip arbore oarecare.

Problema 23 [Explorarea în adâncime a arborilor binari] Se cere implementarea unei clase pentru
stocarea arborilor binari care să suporte explorarea în inordine, preordine și postordine a obiectelor de
tip arbore binar.

Problema 24 [Rutare în grafuri – Algoritmul lui Dijkstra] Se cere un program C++ cu clase pentru
stocarea unui graf (orientat) ponderat și să permită determinarea rutelor minime de la un nod la oricare
din celelalte noduri.

56
Bibliografie
1. Alexandrescu, A., Programare modernă în C++, Teora, 2002.
2. Andonie, R., Gârbacea, I., Algoritmi fundamentali. O perspectiva C++, Editura Libris, 1995.
3. Bălănescu, T., Proiectare și programare orientată pe obiecte, Sinteze, Bibliteca virtuală,
Universitatea Spiru Haret, 2007.
4. Berian, D., Cocoș, A., Programare orientată pe obiecte, UPT, Îndrumător de laborator, 2008.
5. Gălățan, C., Introducere în Standard Template Library, Editura All , 2008.
6. Jamsa K., Klander L., Totul despre C și C++ – Manualul fundamental de programare în C și
C++, Teora, 1999 -2006.
7. Pătruț, B., Muraru, C.V., Aplicații în C și C++, EduSoft, 2006.
8. Schildt H., C++, manual complet. Teora, 1997.
9. ***, C++ Programming Exam ples on Graph Problems & Algorithms,
http://www.sanfoundry.com/cpp -programming -examples -graph -problems -algorithms/
10. ***, Informatică pentru examenul de licență. Univ ersitatea Babeș Bolyai, 2016,
http://www.cs.ubbcluj.ro/wp -content/uploads/Manual_Informatica_2016_RO.pdf

57
II. Baze de date
Nicoleta Magdalena Iacob , Conf. univ. dr., Universitatea Spiru Haret

1. Noțiuni introductive în teoria bazelor de date
1.1. Noțiunile de bază de date, sistem de gestiune a bazei de date

O bază de date (BD) :
• reprezintă un ansamblu structurat de fișiere care grupează datele prelucrate în aplicații
informatice ale unei persoane, grup de persoane, instituții etc. ;
• este definită ca o colecție de date aflate în interdependență, împreună cu descrierea datelor
și a relațiilor dintre ele.

Sistemul de gestiune a baze i de date (SGBD) este:
• un sistem complex de programe care asigură interfața între o bază de date și utilizatorii
acesteia;
• software -ul bazei de date care asigură:
o definirea structurii bazei de date;
o încărcarea datelor în baza de date;
o accesul la baza de da te (interogare, actualizare);
o întreținerea bazei de date (refolosirea spațiilor goale, refacerea bazei de date în
cazul unor incidente);
o reorganizarea bazei de date (restructurarea și modificarea strategiei de acces);
o securitatea datelor.

1.2. Noțiunile d e entitate, relație, atribut

Cele trei concepte de bază utilizate în organizarea bazei de date sunt:
• entitatea;
• atributul;
• valoarea.

Prin entitate se înțelege un obiect concret sau abstract reprezentat prin proprietățile sale. O proprietate
a unui obiect poate fi exprimată prin perechea (ATRIBUT, VALOARE).

Entitatea poate fi persoană, loc, concept, activitate etc. Prin urmare, ea poate fi un obiect cu existență
fizică, reală sau poate fi un obiect cu existență conceptuală, abstractă.

Exemplu : În ex emplul “ Produsul X are culoarea Y”, atributul este „culoare a”, iar valoarea est e
reprezentată litera „Y”.

O entitate poate fi :
• dependentă (slabă), existența s a depinzând de altă entitate;
• independentă (tare), caz în care ea nu depinde de existența altei entități.

Observații privind entitățile:
• entitățile devin tabele în modelele relaționale;
• în general, entitățile se scriu cu litere mari;
• entitățile sunt substantive , dar nu orice substantiv este o entitate. Trebuie ignorate
substantivele nerelevante;

58
• cheia primară identifică unic o entitate și face distincție între valori diferite ale entității.
Aceasta trebuie să fie unică și cunoscută la orice moment. Cheia primară trebuie să fie
controlată de administratorul bazei, să nu conțină informații descriptive , să fie simplă, fără
ambiguități, să fie stabilă, să fie familiară utilizatorului astfel încât acesta să o poată folosi
cu ușurință;
• pentru fiecare entitate este obligatoriu să se dea o descriere detaliată;
• nu pot exista, în aceeași diagramă, două entităț i cu același nume, sau o aceeași entitate cu
nume diferite.

Relația este o comunicare între două sau mai multe entități. Gradul unei relații este dat de numărul de
entități participante într -o relație (de exemplu, relație binară, ternară, cvadruplă, n -ară).
Existența unei relații este subordonată existenței entităților pe care le leagă.

O relație în care aceeași entitate participă mai mult decât o dată în diferite roluri definește o relație
recursivă . Uneori, aceste relații sunt numite unare .

Observații privind relațiile:
• în modelul relațional, relațiile devin tabele speciale sau coloane speciale care referă chei
primare;
• relațiile sunt verbe, dar nu orice verb este o relație;
• pentru fiecare relație este important să se dea o descriere detaliată;
• într-o relație, tuplurile trebuie să fie distincte;
• în aceeași diagramă pot exista relații diferite cu același nume. În acest caz, ele sunt
diferențiate de către entitățile care sunt asociate prin relația respectivă;
• cardinalul relației este num ărul tuplurilor dintr -o relație. P entru fiecare relație trebuie
stabilită cardinalitatea (maximă și min imă) relației .

Asupra entităților participante într -o relație pot fi impuse constrângeri care trebuie să reflecte
restricțiile care există în lumea reală asupra relații lor. O clasă de constrângeri, numite constrângeri de
cardinalitate , este definită de numărul de înregistrări posibile pentru fiecare entitate participantă (raport
de cardinalitate). Cel mai întâlnit tip de relații este cel binar , iar în acest caz rapoartel e de cardinalitate
sunt, în general, one-to-one (1:1), one -to-many ( 1:M) sau many -to-many (N:M ).
• 1:1 – legătura de tip “ una-la-una” (one -to-one) – este asocierea prin care unui element
(entitate) din mulțimea E1 îi corespunde un singur element din mulțimea E2 și reciproc;
• 1:M – legătura de tip “ una-la-multe ” (one -to-many) – este asocierea prin care unui
element din mulțimea E1 îi corespund unul sau mai multe elemente din mulțimea E2, dar
unui element din E2 îi corespunde un singur element în mulțimea E1;
• N:M – legătura de tip “multe -la-multe” (many -to-many) – este asocierea prin care unui
element din mulțimea E1 îi corespund unul sau mai multe elemen te din mulțimea E2 și
reciproc.

O relație se poate memora printr -un tabel de forma:

R A1 … Aj .. Am
r1 a11 … a1j … a1m
… … … … … …
ri ai1 … aij … aim
… … … … … …
rn an1 … anj … anm

59

unde liniile din acest tabel formează elemen tele relației, sau tupluri, înregistrări, care în general sunt
distincte, coloanele A1, A 2, …, A m formeaz ă o mulțime de atribute, iar a11, …, a nm sunt valoari pentru
fiecare din atributele A 1, A m.

Atributul este o proprietate descriptivă a une i entități sau a unei relații (d e exemplu, denumire ,
unitate_de_masura a unui produs , sunt atribute al e entității PRODUS ).

Atributele pot fi :
• simple (de exemplu, prețul unitar al unui produs );
• compuse (de exemplu, denumirea produsului );
• cu valori multiple (de exemplu, lim bile în care e ste tradus un produs );
• derivate (de exemplu, numă rul de zile rămase pănă la expirare se obține din data
expirării ).

Atributele sunt utile atunci când într -o relație un domeniu apare de mai multe ori. Prin numele dat
fiecărei coloane (atribut), se diferențiază coloanele care conțin valori ale aceluiași domeniu, eliminând
dependența față de ordine.

Observații privind atributele:
• atributul reprezintă coloana unei tabele de date, caracterizată printr -un nume;
• trebuie făcută distincție între atribut, care uzual devine coloană în modelele relaționale și
valoarea acestuia, care devine valoare în coloane;
• atributele sunt substantive , dar nu orice substantiv este atribut;
• fiecărui atribut trebuie să i se dea o descriere completă în specificațiile modelului
(exemple, contraexemple, caracteristici);
• pentru fiecare atribut trebuie spe cificat numele, tipul fizic (integer, float, char etc.), valori
posibile, valori implicite, reguli de validar e, constrângeri, tipuri compuse;
• atributele pot caracteriza o clasă de entități, nu doar o entitate.

1.3. Construirea de diagrame entitate -relație

Prin tehnica entiate -relație (denumită și entitate -asociere) se construiește o diagramă entiate -relație
(notată E -R) prin parcurgerea următorilor pași:
a) identificarea entităților (componentelor) din sistemul proiectului;
b) identificarea asocierilor (relații lor) dintre entități și calificarea lor;
c) identificarea atributelor corespunzătoare entităților;
d) stabilirea atributelor de identificare a entităților.

a) Identificarea entităților . Prin convenție, entitățile sunt substantive, se scriu cu litere mari și se
reprezintă prin dreptunghiuri . Într -o diagramă nu pot exista două entități cu același nume, sau o
aceeași entitate cu nume diferite.

b) Identificarea asocierilor dintre entități și calificarea lor . Între majoritatea componentelor (adică a
entităților) unu i sistem economic se stabilesc legături (asocieri).

Observații privind diagramele enti tate-relație :
• legăturile se reprezintă prin arce neorientate între entități;
• fiecărei legături i se acordă un nume plasat la mijlocul arcului și simbolizat printr -un romb
(semnificația legăturii);
• numerele simbolizate deasupra arcelor se numesc cardinalități și reprezintă tipul legăturii;

60
• cardinalitatea asocierilor exprimă numărul minim și maxim de realizări ale unei entități cu
cealaltă entitate asociată. Maximele unei cardinalități sunt cunoscute și sub denumirea de
grad de asociere , iar minimele unei cardinalități reprezintă obligativita tea participării
entităților la asociere.

1.4. Tipuri de relații între entități

Asocierea dintre entități se face în funcție de:
• cardinalitatea asocierii . În funcție de maxima cardinalității (gradul de asociere), se cunosc
trei tipuri de asocieri:
o una-la-una (1-1 sau one to one) ;
o una-la-multe (1-M sau one to many) ;
o multe -la-multe (N -M sau many to many) .
• numărul de entități disti ncte care participă la asociere. Se cunosc trei tipuri de asocieri:
o binare (între două entități distincte);
o recursive (asocieri ale entităților cu ele însele);
o complexe (între mai mult de două entități distincte).

c) Identificarea atributelor entităților și a asocierilor dintre entități . Atributele unei entități reprezintă
proprietăți ale acestora. Atributele sunt substantive , iar pentru fiecare atribut i se va preciza tipul fizic
(integer, float, char, string etc.)

d) Stabilirea atributelor de identificare a entităților . Un atribut de identificare (numit cheie primară),
reprezintă un atribut care se caracterizează prin unicitatea valorii sale pentru fiecare instanță a entității.

În cadrul diagramei entitate -asociere, un atribut de identificare se marchează prin subliniere sau prin
marcarea cu simbolul # plasat la sfârșitul numelui acestuia.

Pentru ca un atribut să fie atribut de identificare, acesta trebuie să satisfacă unele cerințe:
• oferă o identificare unică în cadrul entității;
• este ușor de utilizat;
• este scurt (de cele mai multe ori, atributul de identificare apare și în alte entități, drept
cheie externă).

Pentru o entitate pot exista mai multe atribute de identificare, numite atribute (chei) candidate . Dacă
există mai mulți candidați cheie, se va selecta unul, preferându -se acela cu valori mai scurte și mai
puțin volatile.

2. Baze de date relaționale
2.1. Noțiu nile de bază de date relațională, sistem de gestiune a bazelor de date relaționale

O bază de date relațională (BDR) reprezintă un ansamblu de relații, prin care se reprezintă datele și
legăturile dintre ele.

În cadrul bazei de date relaționale, datele sunt organizate sub forma unor tablouri bidimensionale
(tabele) de date, numite relații . Asocierile dintre relații se reprezintă prin atributele de legătură. În
cazul legăturilor de tip „ una-la-multe ”, aceste atr ibute figurează într -una dintre relațiile implicate în
asociere. În cazul legăturilor de tip „ multe -la-multe ”, atributele sunt situate într -o relație distinctă,
construită special pentru explicarea legăturilor între relații.

61

Prin sistem de gestiune a baze lor de date relaționale (SGBDR) se înțelege un SGBD care utilizează
drept concepție de organizare a datelor modelul relațional.

Definirea unui SGBDR impune o detaliere a caracteristicilor pe care trebuie să le prezinte un SGBD
pentru a putea fi considerat relațional. În acest sens, Codd a formulat (în 1985) 13 reguli, care exprimă
cerințele pe care trebuie să le satisfacă un SGBD.

2.2. Regulile lui Codd

Regulile lui Codd pe ntru SGBD -urile relaționale :
R0: Gestionarea datelor la nivel de relație . Sistemul trebuie să gestioneze BD numai prin mecanisme
relaționale.

R1: Reprezentarea logică a datelor . Într-o bază de date relaționată, informația este reprezentată la nivel
logic sub forma unor tabele (relații). Acest lucru înseamnă că toate datele treb uie să fie memorate și
prelucrate în același mod.

R2: Garantarea accesului la date . Orice dată din baza de date relaționată trebuie să poată fi accesată
prin specificarea numelui relației (tabelei) , a valorii cheii primare și numelui atributului (coloană) .

R3: Valorile nu le:
• sistemul trebuie să permită declararea și manipularea sistematică a valorilor NULL
(semnifică lipsa unor date);
• valorile NULL diferă de șirurile de caractere „spațiu”, șirurile vide de caractere.
• valorile NULL sunt deosebit de importa nte în implementarea restricțiilor de integritate:
integritatea entităților; integritatea referențială .

R4: Metadatele . Utilizatorii autorizați trebuie să poată aplica asupra descrierii bazei de date aceleași
operații ca și asupra datelor obișnuite.

R5: Facilitățile limbajelor utilizate :
• trebuie să existe cel puțin un limbaj care să exprime oricare din următoarele operații:
o definirea relațiilor;
o să vizualizeze datele;
o să regăsească informația;
o să poată reactualiza informația;
o să verifice și să corecteze d atele de intrare etc.
• în general, toate implementările SQL respectă această regulă.

R6: Actualizarea tabelelor virtuale :
• toate tabelele/relațiile virtuale trebuie să poată fi actualizate;
• nu toate tabelele virtuale sunt teoretic actualizate.

Exemplu : Fie tabela de bază PROD US, cu următoarea schemă PROD US (Denumire_produs :D1,
Cant itate:D2, Pret _unitar :D3), cu ajutorul tabelei PROD US este definită o tabelă virtuală FACTURA ,
cu schema: FACTURA (Denumire_produs :D1, Cant itate:D2, Pret _unitar :D3, Val oare:D4). Valorile
atributului „Val oare” se calculează astfel:
Valoare=Cantitate*Pret_unitar

62
Presupunem că se dorește schimbarea prețului unitar la un anumit produs, această schimbare trebuie
efectuată în tabela de bază PROD US, atributul „Pret _unitar” din tabela vi rtuală FACTURA , fiind
actualizabil, întrucât actualizarea se poate propaga spre tabela de bază.

Presupunem că se dorește schimbarea valorii ( Valoare) la un anumit produs:
 modificarea de la tabela virtuală spre tabela de bază nu mai este posibilă, atributu l
„Val oare” nu este actualizabi l, deoarece schimbarea valorii ( Valoare) se poat e datora
schimbării cantității ( Cant itate) și/sau a prețului unitar ( Pret_unirar );
 astfel trebuie să existe un mecanism prin care să se poată determina dacă anumite
vizualizări pot fi modificate sau nu.

Majoritatea implementărilor SQL îndeplinesc această cerință.

R7: Actualizările în baza de date (inserările, modificările și ștergerile din baza de date ):
• un SGBDR nu trebuie să oblige utilizatorul să caute într -o relație, tuplu cu tuplu, pentru a
regăsi informația dorită;
• această regulă exprimă cerința ca în operațiile prin care se schimbă conținutul bazei de
date să se lucreze la un moment dat pe o întreagă relație.

R8: Independența fizică a datelor :
• o schimbare a structurii fizice a datelor nu trebuie să blocheze funcționarea programelor
de aplicații;
• într-un SGBDR trebuie să se separe aspectul fizic al datelor (stocare sau acces la date) de
aspectul logic al datelor.

R9: Independența logică a datelor . Schimbarea relațiilor bazei de date nu trebuie să afecteze
programele de aplicație.

R10: Restricțiile de integritate . Restricțiile de integritate trebuie să fie definite într -un limbaj
relațional, nu în programul de aplicație.

R11: Distribuirea geografică a datelor . Distribuirea datelor pe mai multe calculatoare dintr -o rețea de
comunicații de date nu trebuie să afecteze programele de aplicație.

R12: Prelucrarea datelor la nivelul de bază . Dacă sistemul posedă un limbaj de bază orientat pe
prelucrarea de tupluri și nu pe prelucrarea relațiilor, acest limbaj nu trebuie să fie utilizat pentru a evita
restricțiile de integritate (se introduc inconsistențe).

Clasificarea regulilor lui Codd
În funcție de tipul de cerințe pe care le exprimă, regulile sunt grupate în 5 cate gorii:
1) reguli de bază: R0 și R12;
2) reguli structurale: R1 și R6;
3) reguli privind integritatea datelor: R3 și R10;
4) reguli privind manipularea datelor: R2, R4, R5, R7;
5) reguli privind in dependența datelor: R8, R9, R11.

2.3. Componentele bazelor de date relați onale:

Orice model de date, conform unei sugestii a lui Codd, trebuie să se bazeze pe trei componente:
1) structurile de date. O bază de date relațională (BDR) reprezintă un ansamblu de relații, pr in
care se reprezintă date și legăturile dintre ele. Structurile sunt definite de un limbaj de definire

63
a datelor (Data Definition Language). Datele în modelul relațional sunt structurate în relații
(tabele) bidimensionale;
2) operatorii de manipulare a datelor . Relațiile pot fi manip ulate utilizând un limbaj de
manipularea datelor (Data Manipulation Language). În modelul relaționa l, limbajul folosește
operatori relaționali bazați pe conceptul algebr ei relaționale. În afară de ace sta, există limbaje
echivalente algebrei relaționale, cu m ar fi calculul relațional orientat pe tuplu și calculul
relațional orientat pe domeniu ;
3) constrângerile de integritate . Prin integritatea datelor se subînțelege că datele rămân stabile, în
siguranță și corecte. Integritatea în modelul relațional este menț inută de constrângeri interne
care nu sunt cunoscute utilizatorului.

1) Structura relațională a datelor
Prezentarea structurii relaționale a datelor impune definirea noțiunilor de: domeniu, tabel ă (relați e),
atribut, tuplu, chei e și schema tabelei.

Dome niul este un ansamblu de valori caracterizat printr -un nume. El poate fi explicit sau implicit.

Tabela/relația este un subansamblu al produsului cartezian al mai multor domenii, caracterizat printr –
un nume, prin care se definesc atributele ce aparțin aceleași clase de entități.

Atributul este coloana unei tabele, caracterizată printr -un nume.

Cheia este un atr ibut sau un ansamblu de atribute care au rolul de a identifica un tuplu dintr -o tabelă.
Tipuri de chei: primare/alternate, simple/comune, externe.

Tuplul este linia dintr -o tabelă și nu are nume. Ordinea liniilor (tupluri lor) și coloanelor (atribute lor)
dintr -o tabelă nu trebuie să prezinte nici -o importanță.

Schema tabelei este formată din numele tabelei, urmat între paranteze rotunde de lista atributelor, iar
pentru fiecare atribut se precizează domeniul asociat.

Schema bazei de date poate fi reprez entată printr -o diagramă de structură în care sunt puse în evidență
și legăturile dintre tabele. Definirea legăturilor dintre tabele se face logic construind asocieri între
tabele cu ajutorul unor atribute de legătură. Atributele implicate în realizarea le găturilor se găsesc fie
în tabelele asociate, fie în tabele distincte construite special pentru legături. Atributul din tabela inițială
se numește cheie externă , iar cel din tabela finală este cheie primară . Legăturile posibile sunt 1:1,
1:M, N:M . Potenția l, orice tabelă se poate lega cu orice tabelă, după orice atribute.

Legăturile se stabilesc la momentul descrierii datelor prin limbaje de descriere a datelor (LDD), cu
ajutorul restricțiilor de integritate. Practic, se stabilesc și legături dinamice la momentul execuției.

2) Operatorii modelului relațional
Operatorii modelului relațional sunt operatorii din:
a) algebra relațională;
b) calcul relațional: orientat pe tuplu; orientat pe domeniu

a) Algebra relațională este o colecție de operații formale aplicate asupra tabelelor (relațiilor), și a
fost concepută de E.F. Codd. Operațiile sunt aplicate în expresiile algebrice relaționale care sunt
cereri de regăsire. Acestea sunt compuse din operatorii relaționali și ope ranzi. Operanzii sunt
întotdeauna tabele (una sau mai multe). Rezultatul evaluării unei expresii relaționale este format
dintr -o singură tabelă.

64

Algebra relațională are cel puțin puterea de regăsire a calcului relațional. O expresie din calculul
relațion al se poate transforma într -una echivalentă din algebra relațională și invers.

Codd a introdus 6 operatori de bază (reuniunea, diferența, produsul cartezian, selecția, proiecția,
joncțiunea) și 2 operatori derivați (intersecția și diviziunea). Ulterior a u fost introduși și alți operatori
derivați (speciali). În acest context, operatorii din algebra relațională pot fi grupați în două categorii:
operatori pe mulțimi și operatori speciali.
Fie R1, R2, R3 – relații (tabele).

Operator ii pe mulțimi sunt:
• reuniunea. R3 = R1 ∪ R2, unde R3 va conține tupluri din R1 sau R2 luate o singură dată;
• diferența. R3 = R1 \ R2, unde R3 va conține tupluri din R1 care nu se regăsesc în R2;
• produsul cartezian . R3 = R1 × R2, unde R3 va conține tupluri construite din perechi
(x1,x2), cu x1 ∈R1 și x2∈R2;
• intersecția. R3 = R1 ∩ R2, unde R3 va conține tupluri care se găs esc în R1 și R2 în același
timp etc.

Operatori i relaționali speciali sunt:
• selecția. Din R1 se obține o subtabelă R2, care va conține o submulțime din tuplurile
inițiale din R1 ce satisfac un predicat (o condiție). Numărul de atribute din R2 este egal cu
numărul de atribute din R1. Numărul de tupluri din R2 este mai mic d ecât numărul de
tupluri din R1;
• proiecția. Din R1 se obține o subtabelă R2, care va conține o submulțime din atributele
inițiale din R1 și fără tupluri duplicate. Numărul de atribute din R2 este mai mic de cât
numărul de atribute din R1;
• joncțiunea este o derivație a produs ului cartezian, ce presupune utilizarea unui calificator
care să permită compararea valorilor unor atribute din R1 și R2, iar rezultatul în R3. R1 și
R2 trebuie să aibă unul sau mai multe atribute comune care au valori comune.

b) Calculul relațional se bazează pe calculul predicatelor de ordinul întâi și a fost propus de E.F.
Codd. Predicatul este o relație care se stabilește între anumite elemente și care poate fi confirmată sau
nu. Predicatul de ordinul 1 este o relație care are drept argumente variabil e care nu sunt predicate.
Variabila poate fi de tip tuplu (valorile sunt dintr -un tuplu al unei tabele) sau domeniu (valorile sunt
dintr -un domeniu al unei tabele). Cuantificatorii (operatorii) utilizați în calculul relațional sunt:
universal ( ∀) și existențial ( ∃).

Construcția de bază în calculul relațional este expresia relațională de calcul tuplu sau domeniu.

Expresia relațională de calcul este formată din:
• operația de efectuat;
• varia bile (tuplu respectiv domeniu);
• condiții (de comparație, de existență);
• formule bine definite (operanzi -constante, variabile, funcții, predicate, operatori);
• cuantificatori.

Pentru implementarea acestor operatori există comenzi specifice în limbajele de manipulare a datelor
(LMD) din sistemele de gestiune a bazel or de date relaționale (SGBDR). Aceste comenzi sunt utilizate
în operații de regăsire (interogare).

65
După tehnica folosită la manipulare, LMD sunt bazate pe:
• calculul relațional (QUEL în Ingres, ALPHA propus de Codd);
• algebra relațională (ISBL, RDMS);
• transformare (SQL, SQUARE);
• grafică (QBE, QBF).

Transformarea oferă o putere de regăsire echivalentă cu cea din calculul și algebra relațională. Se
bazează pe transformarea (mapping) unui atribut sau grup de atribute într -un atribut dorit prin
intermediul unor relații. Rezultatul este o relație (tabelă) care se poate utiliza într -o altă transformare.

Grafica oferă interactivitate mare pentru constr uirea cererilor de regăsire. Utilizatorul specifică cere rea
alegând sau completând un ecran structurat grafic. Poate fi folosit ă de către toate categoriile de
utilizatori în informatică.

Algebra relațională este prin definiție neprocedurală (descriptivă), iar calculul relațional permite o
manieră de căutare mixtă (procedurală/neprocedurală).

3) Restricții de integritate ale mode lului relațional
Restricțiile de integritate ale modelului relațional reprezintă cerințe pe care trebuie să le îndeplinească
datele din cadrul bazei de date pentru a putea fi considerate corecte și coerente în raport cu lumea reală
pe care o reflectă. Dacă o bază de date nu respectă aceste cerințe, ea nu poate fi utilizată cu un maxim
de eficiență.

Restricțiile sunt de două tipuri: restricții de integritate structural ă și restricții de integritate de
comportament .

Restricții de integritate structurale , care se definesc prin egalitatea sau inegalitatea unor valori din
cadrul relațiilor . Acestea sunt :
• restricția de unicitate a cheii – cheia primară trebuie să fie unică și minimală ;
• restricția de integritate a referirii . Într -o tabelă t1 care referă o tabelă t2, valorile cheii
externe trebuie să figureze printre valorile cheii primare din t2 sau să ia valoarea NULL
(neprecizat);
• restricția de integritate a entității . Într -o tabelă, atributele din cheia primară nu trebui e să
ia valoarea NULL.

Cele trei restricții de mai sus sunt minimale.

Pe lângă acestea, există o serie de alte restricții structurale care se referă la dependențele dintre date:
funcționale, multivaloare, joncțiune etc. (sunt luate în considerare la te hnicile de proiectare a bazelor
de date relaționale – BDR).

Restricții de integritate de comportament – sunt cele care se definesc prin comportamentul datelor și
țin cont de valorile din BDR . Acestea sunt :
• restricția de domeniu . Domeniul corespunzător un ui atribut dintr -o tabelă trebuie să se
încadreze între anumite valori;
• restricții temporal e. Valorile anumitor atribute se compară cu niște valori temporal e
(rezultate din calcule etc.).

Restricțiile de comportament fiind foarte generale se gestionează fie la momentul descrierii datelor (de
exemplu prin clauza CHECK), fie în afara modelului la momentul execuției.

66
2.4. Tipuri de constrângeri de integritate

Restricțiile de integritate suportate de Oracle sunt:
• NOT NULL – nu permite valori NULL în coloanele unei tabele;
• UNIQUE – nu sunt permise valori duplicat în coloanele unei tabele;
• PRIMARY KEY – nu permite valori duplicate sau NULL în coloana sau coloan ele definite
astfel;
• FOREIGN KEY – presupune ca fiecare valoare din coloana sau setul de coloane definit
astfel să aibă o valoare corespondentă identică în tabela de legătură, tabelă în care coloana
corespondentă este definită cu restricția UNIQUE sau PRI MARY KEY;
• CHECK – elimină valorile care nu satisfac anumite cerințe (condiții) logice.

Termenul de chei e (keys) este folosit pentru definirea câtorva categorii de constrângeri , și sunt :
PRIMARY KEY, UNIQUE KEY, FOREIGN KEY, REFERENCED KEY .

3. Proiectarea bazelor de date relaționale

Proiectarea BDR se realizează prin proiectarea schemelor BDR și proiectarea modulelor funcționale
specializate.

A. Proiectarea schemelor BDR
Schemele bazei de date sunt: conceptuală, externă și internă.

a) Proiectarea schemei conceptuale pornește de la identificarea setului de date necesar sistemului.
Aceste date sunt apoi integrate și structurate într -o schemă (exemplu: pentru BDR relaționale cea mai
utilizată tehnică este normalizarea). Pentru acest lucru se p arcurg pașii:
• stabilirea schemei conceptuale inițiale – care se deduce din modelul entitate -asociere.
Pentru acest lucru, se transformă fiecare entitate din model într -o colecție de date (fișier),
iar pentru fiecare asociere se definesc cheile aferente. D acă rezultă colecții izolate, acestea
se vor lega de alte colecții prin chei rezultând asocieri ( 1:1, 1:M, N:M );
• ameliorarea progresivă a schemei conceptuale – prin eliminarea unor anomalii (exemplu:
cele cinci forme normale pentru BDR relaționale);
• stabilirea schemei conceptuale finale – trebuie să asigure un echilibru între cerințele de
actualizare și performanțele de exploatare (exemplu: o formă normală superioară asigură
performanțe de actualizare, dar timpul de răspuns va fi mai mare).

Tehnica de normalizare
Tehnica de normalizare este utilizată în activitatea de proiectare a structurii BDR și constă în
eliminarea unor anomalii (neajunsuri) de actualizare din structură.

Anomaliile de actualizare sunt situații nedorite care pot fi generate de a numite tabele în procesul
proiectării lor:
• anomalia de ștergere – stergând un tuplu dintr -o tabelă, pe lângă informațiile care trebuie
șterse, se pierd și informațiile utile existente în tuplul respectiv;
• anomaliile de adăugare – nu pot fi incluse noi in formații necesare într -o tabelă, deoarece
nu se cunosc și alte informații utile (de exemplu valorile pentru cheie);
• anomalia de modificare – este dificil de modificat o valoare a unui atribut atunci când ea
apare în mai multe tupluri.

67
Normalizarea este o teorie construită în jurul conceptului de forme normale (FN), care ameliorează
structura BDR prin înlăturarea treptată a unor neajunsuri și prin imprimarea unor facilități sporite
privind manipularea datelor.

Normalizarea utilizează ca metodă descompun erea (top -down) unei tabele în două sau mai multe
tabele, păstrând informații (atribute) de legătură.

Codd a definit inițial 3 forme normale, notate prin FN1, FN2 și FN3. Întrucât într -o primă formulare,
definiția FN3 ridică ceva probleme, Codd și Boyce au elaborat o nouă variantă, cunoscută sub numele
de Boyce -Codd Normal Form (BCNF) . Astfel , BCNF este reprezentată separat în majoritatea
lucrărilor. R. Fagin a tratat cazurile FN4 și FN5.

O relație este într -o formă normală dacă satisface o mulțime de co nstrângeri.

Normalizarea bazei de date relaționale poate fi imaginată ca un proces prin care pornindu -se de la
relația inițială/universală R se realizează descompunerea succesivă a acesteia în subrelații, aplicând
operatorul de proiecție . Relația R poate fi ulterior reconstruită din cele n relații obținute în urma
normalizării, prin operații de joncțiune .

3.1. Formele normale: FN1; FN2; FN3

Prima formă normală (FN1)
FN1 este strâns legată de noțiunea de atomicitate a atributelor unei relații. Astfel, aducerea unei relații
în FN1 presupune introducerea noțiunilor de:
• atribut simplu (atribut atomic) – atribut care nu mai poate fi descompus în alte atribute, în
caz contrar, atributul este compus (atribut neatomic);
• atribut compus ;
• grupuri repetitive de atribute – atribut (grup de atribute) dintr -o relație care apare cu valori
multiple pentru o singură apariție a cheii primare a relației nenormalizate.

Aducerea unei relații universale în FN1
FN1 este tratată în general cu superficialitate, deoarece principala cerință – atomicitatea valorilor –
este ușor de îndeplinit (cel puțin la prima vedere).

Dintre toate formele normale, doar FN1 are caracter de obligativitate. Se spune că o bază de date este
normalizată da că toate relațiile se află măcar în FN1 .

O relație este în FN1 dacă domeniile pe care sunt definite atributele relației sunt constituite numai din
valori atomice. Un tuplu nu trebuie să conțină atribute sau grupuri de atribute repetitive.

Aducerea relațiilor în FN1 presupune eliminarea atribu telor compuse și a celor repetitive.

Se cunosc trei soluții pentru determinarea grupurilor repetitive :
1) eliminarea grupurilor repetitive pe orizontală (în relația R inițială, în locul atributelor
compuse se trec componentele acestora, ca atribute simple);
2) eliminarea grupurilor repetitive prin adăugarea de tupluri;
3) eliminarea grupurilor repetitive prin construirea de noi relații.

Primele două metode generează relații stufoase prin duplicarea forțată a unor atribute, respectiv
tupluri, creându -se astfel redu ndanțe masive cu multiple anomalii de actualizare.

68
Metoda a treia presupune eliminarea grupurilor repetitive prin construirea de noi relații, ceea ce
generează o structură ce oferă cel mai mic volum de redundanță.

Exemplu: Fie relația nenormalizată (primară) FACTURI. Să se stabilească o structură de tabele care să
permită stocarea informațiilor conținute în document (factură) și obținerea unor situații sintetice
privind evidența sumelor facturate pe produse, pe clienți, pe anumite perioade de timp.

Relația FACTURI nenormalizată

În cazul în care o factură conține mai multe produse, relația de mai sus va avea grupurile repetitive:
„cod_produs”, „denumire_produs”, „data_expirarii”, „cantitate”, „pret_unitar”, „valoare”,
„valoare_tva”.

Etapele de aducere a unei relații în FN1 sunt:
I. se construiește câte o relație pentru fiecare grup repetitiv;
II. în schema fiecărei noi relații obținute la pasul 1 se introduce și cheia pr imară a relației R
nenormalizată ;
III. cheia primară a fiecărei noi relații va fi compusă din atributele chei ale relației R, plus unul
sau mai multe atribute proprii.

Exemplu: Deoarece o factură poate avea unul sau mai multe produse înscrise pe aceasta, informațiile
legate de produse vor fi separate într -o altă tabelă. Aplicând etapele d e aducere în FN1, se obțin două
relații .

FACTURI

nr_factura#
data_factura
nume_client
adresa_client
telefon_client
email_client
banca_client
nr_cont_client
delegat
cod_produs
denumire_produs
unitate_de_masura
data_expirarii
cantitate
pret_unitar
valoare
valoare_tva
total_valoare_factura
total_valoare_tva

69

Relația FACTURI adusă în forma normală FN1

Observații:
• Câmpul „adresa_client” cuprinde informații despre județul, localitatea, strada și numărul
domicililului clientului. Dacă se consideră că este de interes o evidență a sumelor
factorizate pe județe sau localități, se vor pune în locul câmpului „adresa_client” trei
câmpuri distincte: „judet_client”, „localitate_client”, „ adresa_client ”, ușurând în acest fel
interogările;
• Între tabela FACTURI și tabela LINII_FACTU RI există o relație de „una -la-multe ”, adică
unui număr u nic de factură îi pot corespunde unul sau mai multe produse care sunt
memorate ca înregistrări în tabela LINII_FACTURI. Cheia primară în această tabelă este
o cheie compusă, formată din două câmpuri: „ nr_factura ” și „ cod_produs ”.

Însă, eliminarea grupurilor repetitive , adică aducerea unei relații în FN1, nu rezolvă complet problema
normalizării.

A doua formă normală (FN2)
FN2 este strâns legată de noțiunea de dependență funcțională (DF) .

O relație se află în a doua formă normală (FN2) dacă:
1. se află în forma normală FN1 și
2. fiecare atribut , care nu este cheie , este dependent de întreaga cheie primară.

Etapele de aducere a unei relații de la FN1 la FN2 sunt:
I. se identifică posibila cheie primară a relației aflate în FN1;
II. se identifică toate dependențele dintre atributele relației, cu excepția acelora în care sursa
este un atribut component al cheii primare;
III. se identifică toate dependențele care au ca sursă un atribut sau subansamblu de atribute din
cheia primară;
IV. pentru fiecare atribut (sau subansamblu) al cheii de la pasul III se creează o relație care va
avea cheia primară atributul (subansamblul) respectiv, iar celelalte atribute vor fi cele care
apar ca destinație în dependențele de la etapa III.

Exemplu: Relația care conține date redundante (de exemplu, modificarea denumirii unui produs atrage
după sine m odificarea în fiecare tuplu în care apare acest produs) este relația LINII_FACTURI. Se
observă că nu există nici o dependență funcțională între atributele necomponente ale cheii. În schimb,
toate atributele care nu intră în alcătuirea cheii compuse sunt de pendente de aceasta, iar DF dintre FACTURI

nr_factura#
data_factura
nume_client
adresa_client
telefon_client
banca_client
nr_cont_client
delegat
toal_valoare_factura
toal_valoare_tva
LINII_FACTURI

nr_factura#
cod_produs#
denumire_produs
unitate_de_masura
data_expirarii
cantitate
pret_unitar
valoare
valoare_tva

70
atributul component al cheii primare sunt: cod_produs –> denumire_produs, cod_produs –>
unitate_de_masura , data_expirarii . Ca urmare se formează încă două relații.

Relația FACTURI în a doua formă normală FN2

Chiar dacă au fost eliminate o parte din redundanțe, mai rămân și alte redundanțe ce se vor elimina
aplicând alte forme normale.

A treia formă normală (FN3)
O relație este în forma normală trei (FN3) dacă:
1. se găsește în FN2 și
2. fiecare atribut care nu este cheie (nu participă la o cheie) depinde direct de cheia primară.
A treia regulă de normalizare cere ca toate câmpurile din tabele să fie independente între
ele.

Exemplu : În relația FACTURI se observă că atributul „nume_client” determină în mod unic atributele
„adresa_client”, „telefon_client”, „email_client”, „banca_client” și „nr_cont_client”. Deci pentru
atributul „nume_client” se construiește o relație CLI ENTI în care cheia primară va fi acest atribut, iar
celelalte atribute vor fi „adresa_client”, „telefon_client”, „email_client”, „banca_client” și
„nr_cont_client”. Câmpurile „valoare” și „valoare_tva” depind de câmpurile „cantitate”, „pret_unitar”,
și de un procent fix de TVA. Fiind câmpuri ce se pot calcula în orice moment, ele vor fi eliminate din
tabelă LINII FACTURI, deoarece constituie informație memorată redundant.

Relația FACTURI în a treia forma normală FN3

Etapele de aducere a unei relații de la FN2 la FN3 sunt: FACTURI

nr_factura#
data_factura
nume_client
adresa_client
telefon_client
email_client
banca_client
nr_cont_client
delegat
toal_valoare_factura
toal_valoare_tva
LINII_FACTURI

nr_factura#
cod_produs#
cantitate
pret_unitar
valoare
valoare_tva
PRODUSE

cod_produs#
denumire_produs
unitate_de_mas ura
data_expirarii

FACTURI

nr_factura#
data_factura
nume_client
delegat
toal_valoare_factura
toal_valoare_tva
LINII_FACTURI

nr_factura#
cod_produs#
cantitate
pret_unitar
PRODUSE

cod_produs#
denumire_produs
unitate_de_masura
data_expirarii

CLIENTI

nume_client#
adresa_client
telefon_client
email_client
banca_client
nr_cont_client

71
I. se identifică toate atributele c e nu fac parte din cheia primară și sunt surse ale unor
dependențe funcționale;
II. pentru aceste atribute, se construiește câte o relație în care cheia primară va fi atributul
respectiv, iar celelalte atribute, destinațiile din DF considerate;
III. din relația de la care s -a pornit se elimină atributele destinație din DF identificată la pasul
I, păstrându -se atributele surse.

Observații :
• Această a treia formă normală mai poate suferi o serie de rafinări pentru a putea obține o
structură performantă de tabele ale bazei de date. De exemplu , se observă că
„nume_client” este un câmp în care este înscris un text destul de lung format dintr -o
succesiune de litere, semne speciale (punct, virgulă, cratimă), spații, numere. Ordonarea și
regăsirea informațiilor după astfel de câmpuri este lentă și m ai greoaie decât după câmpuri
numerice. Din acest motiv se poate introduce un nou atribut „cod_client” care să fie
numeric și care să fie cheia primară de identificare pentru fiecare client;
• O altă observație care poate fi făcută în legătură cu tabelele af late în cea de a treia formă
normală este aceea că „total_valoare_factura” este un câmp care ar trebui să conțină
informații sintetice obținute prin însumarea valorii tuturor ofertelor aflate pe o factură.
Este de preferat ca astfel de câmpuri să fie calcu late în rapoarte sau interogări și să nu fie
memorate în tabelele bazei de date.

Verificarea aplicării corecte a procesului de normalizare se realizează astfel încât uniunea acestor
relații să producă relația inițială, cu alte cuvinte, descompunerea este fără pierderi.

Celelalte forme normale se întâlnesc mai rar în practică. Aceste forme nu sunt respectate, în general,
pentru că beneficiile de eficiență pe care le ad uc nu compensează costul și munca de care este nevoie
pentru a le respecta.

Forma normală Boyce Codd (FNBC)
O definiție mai riguroasă pentru FN3 a fost dată prin forma intermediară BCNF (Boyce Codd Normal
Form):
• o tabelă este în BCNF dacă fiecare determi nant este un candidat cheie. Determinantul este
un atribut elementar sau compus față de care alte atribute sunt complet dependente
funcțional.

A patra formă normală (FN4)
O tabelă este în FN4 dacă și numai dacă :
• este în FN3 și
• nu conține două sau mai multe dependențe multivaloare. Într -o tabelă T, fie A, B, C trei
atribute. În tabela T se menține dependența multivaloare A dacă și numai dacă mulțimea FACTURI

nr_factura#
data_factura
nume_client
delegat

LINII_FACTURI

nr_factura#
cod_produs#
cantitate
pret_unitar
PRODUSE

cod_produs#
denumire_produs
unitate_de_masur
a
data_expirarii

CLIENTI

cod_client#
nume_client
adresa_client
telefon_client
email_client
banca_client
nr_cont_client

72
valorilor lui B ce corespunde unei perechi de date (A,C), depinde numai de o valoare a lui
A și este ind ependentă de valorile lui C.

A cincea formă normală (FN5) (numită și forma normală proiecție -uniune)
O tabelă este în FN5 dacă și numai dacă :
• este în FN4 și
• orice dependență de uniune a lui R este o consecință a unei chei candidat a lui R. În tabela
T (A,B,C) se menține dependența joncțiune (AB,AC) dacă și numai dacă T menține
dependența multivaloare A –>> B sau C.

Fiecare dintre cele 6 forme normale este mai restrictivă ca predecesoarea sa. Astfel, de exemplu, o
schemă de relație aflată în forma normală trei este și în forma normală doi, așa cum se reprezintă în
figura de mai jos:

Forme normale

Scopul formelor normale este acela de a elimina redundanțele din cadrul relațiilor prin descompunerea
acestora în două sau mai multe relații, f ără însă a pierde informație, ceea ce înseamnă faptul că este
posibilă, în orice moment, revenirea la relația originară doar pe baza relațiilor obținute din
descompunere.

Dependența multivaloare este caz particular al dependenței joncțiune. Dependența fu ncțională este caz
particular al dependenței multivaloare.

b) Proiectare a schemei externe are rolul de a specifica viziunea fiecărui utilizator asupra BDR. Pentru
acest lucru, din schema conceptuală se identifică datele necesare fiecărei viziuni. Datele obținute se
structurează logic în subscheme ținând cont de facilitățile de utilizare și de cerințele utilizator. Schema
externă devine operațională prin construirea unor vizualizări (view) cu SGBD -ul și acordarea
drepturilor de acces. Datele într -o vizuali zare pot proveni din una sau mai multe colecții și nu ocupă
spațiul fizic.

c) Proiectarea schemei interne presupune stabilirea structurilor de memorare fizică a datelor și
definirea căilor de acces la date. Acestea sunt specifice fie SGBD -ului (scheme de alocare), fie
sistemului de operare. Proiectarea schemei interne înseamnă estimarea spațiului fizic pentru BDR,
definirea unui model fizic de alocare ( trebuie văzut dacă SGBD -ul permite explicit acest lucru) și
definirea unor indecși pentru accesul direct , după cheie, la date.

B. Proiectarea modulelor funcționale specializate
Proiectarea modulelor funcționale ține cont de concepția generală a BDR, precum și de schemele
proiectate anterior. În acest sens, se proiectează fluxul informațional, modulele de încărcare și
FN 5FN 4 FNBC FN 3FN 2FN 1

73
manipulare a datelor, interfețele specializate, integrarea elementelor proiectate cu organizarea și
funcționarea BDR.

Realizarea componentelor logice . Componentele logice ale unei BD sunt programele de aplicație
dezvoltate, în cea mai mare parte, în SGBD -ul ales. Programele se realizează conform modulelor
funcționale proiectate în etapa anterioară. Componentele logice țin cont de ieșiri, intrări, prelucrări și
de colecțiile de date. În paralel cu dezvoltarea programelor de aplicații se întocmesc și diferite
documentații (tehnică, de exploatare, de prezentare).

Punerea în funcțiune și exploatarea . Se testează funcțiile BDR mai întâi cu date de test, apoi cu date
reale. Se încarcă datele în BDR și se efectuează procedurile de manipulare, de către beneficiar cu
asistența proiectantului. Se definitivează documentațiile aplicației. Se intră în exploatar e curentă de
către beneficiar conform documentaț iei.

Dezvoltarea sistemului . Imediat după darea în exploatare a BDR, în mod continuu, pot exista factori
perturbatori care generează schimbări în BDR. Factorii pot fi: organizatorici, datorați progresului
tehnic, rezultați din cerințele noi ale beneficiarului, din schimbarea metodologiilor etc.

4. Limbajul SQL (Structured Query Language)

Limbajul SQL (Structured Query Language) este limbajul utilizat de majoritatea sistemelor de baze de
date relaționale (SGBDR) pentru definirea și manipularea datelor.

4.1. Structura lexicală a limbajului SQL

Elementele unei instrucțiuni (statement) sunt:
• cuvinte cheie (key words) – dintre care fac parte comenzile (SELECT, UPDATE, INSERT
etc), operatorii (AND, OR, NOT, LIKE), clauzele (WHERE, SET, VALUES etc);
• identificatori (identifier) – sunt elementele care denumesc tabela, coloana sau alt obiect al
BD. SQL face diferența între li terele mari și mici, deci este „case -sensitive”;
identificatorul care conține ghilimele se numește identificator delimitat ;
• constante (literal i) – reprezintă șiruri de caractere (‘ ‘), numere înt regi, numere reale (ex.
3.5; 4. ; .001; 5e2), constanta NULL, constante de tip logic (1 pentru TRUE și 0 pentru
FALSE);
• caractere special e – cum ar fi (;) care se mnifică terminarea comenzilor; (.) care semnifică
virgula zecimală; sau (*) care simbolizează operatorul de înmulțire.

4.2. Operatori SQL

SQL are următorii operatori:
• operatori aritmetici binari :
+

*
% modulo
^ ridicarea la putere
& AND orientat pe biți
| OR orientat pe biți
# XOR orientat pe biți

74
<< deplasare la stânga
>> deplasare la dreapta

• operatori binari de comparație :
<
>
<=
>=
=
<> sau ! = diferit
• operatori aritmetici mari :
@ valoarea absolută
! factorial
!! factorial, operator postfix
~ NOT orientat pe biți
• operatori de comparație :
A BETWEEN min AND max (compară A cu două valori: min și max)
A IN (v1,…,vn) compară A cu o listă de valori
A IS NULL
A IS NOT NULL
A LIKE model_șir
• operatori logici :
Operatorii logici sunt legați prin cuvintele cheie AND, OR, NOT și returnează o valoare
logică TRUE, FALSE sau NULL.
• operatori relaționali :
UNION (reuniune)
INTERSECT (intersecție)
MINUS (diferenț ă)

4.3. Funcții definite în SQL

Categorii de funcții SQL:
• funcții pe un sigur rând (funcții scalare) – realizează operații asupra unui singur rând și
returnează un singur rezultat pentru fiecare rând. Funcțiile pe un sigur rând cuprind
următoarele tipuri de funcții:
o funcții de tip caracter – acceptă argumente de tip caracter și întorc rezultate de tip
caracter (CHR, CONCAT, INITCAP, LOWER, LPAD, LTRIM, REPLACE, RPAD,
RTRIM, SUBSTR, UPPER etc.) sau numeric (ASCII, INSTR, LENGTH);
o funcții de tip numeric (de calcul trigonometric : sin, cos, tg, ctg etc.; de calcul al
logaritmului : ln, log, lg; de calcul al puterilor : pow; de rotunjire : floor, ceil etc.) –
acceptă argumente de tip numeric și întorc rezultate de tip numeric;
o funcții de tip dată calendaristică (ADD_MONTHS, LAST_DAY, MONTHS_
BETWEEN, NEXT_DAY, SYSDATE etc.) – acceptă argumente de tip dată
calendaristică și întorc rezultate de tip dată calendaristică cu excepția funcției
MONTH_BEETWEEN care întoarce o valoare numerică;
o funcții de conversie (TO_ CHAR, TO_NUMBER, TO_DATE, CAST) – fac conversia
dintr -un tip de dată în altul;
o funcții generale : NVL, DECODE.

• funcții pe mai mutle rânduri (funcții de grup) – lucrează cu grupuri de rânduri pentru a
returna un singur rezultat pentru fiecare grup. Aceste funcți i sunt cunoscute cu denumirea

75
de funcții de grup . Toate funcțiile de grup, mai puțin COUNT(*) ignoră valorile NULL.
Majoritatea funcțiilor de grup acceptă opțiunile: DISTINCT (determină luarea în calcul
numai a valorilor distincte ale rândurilor din cadrul grupului) și ALL (este implicit și
determină luarea în calcul a tuturor valorilor grupului de rânduri).

Funcțiile agregat – calculează un rezultat din mai multe linii ale unui tabel (funcții de totalizare):
o COUNT (furnizează numărul de linii ale unui rez ultat);
o SUM (execută suma tuturor valorilor dintr -o coloană);
o MAX (returnează valoarea cea mai mare dintr -o coloană);
o MIN (returnează valoarea cea mai mică dintr -o coloană);
o AVG (calculează media valorilor dintr -o coloană).

Aceste funcții vor fi folosite în instrucțiunea SELECT.

Funcțiile scalare – primesc unul sau mai multe argumente și returnează valoarea calculată sau NULL
în caz de eroare. Argumentele funcțiilor pot fi constante sau valori ale atributelor specificate prin
numele coloanelor corespunzăt oare.

4.4. Tipuri de date

În limbajul SQL sunt definite mai multe tipuri de date: numeric, șir de caractere, șir de biți, dat ă
(calendaristică), timp.

Denumirile tipurilor de date precum și limitele acestora diferă de la un SGBD la altul, dar în general,
sunt destul de asemănătoare.

• Tipul numeric include :
 numere întregi :
o INTEGER sau INT reprezentat pe 4 octeți;
o SMALLINT reprezentat pe 2 octeți;
 numere reale reprezentate în virgulă flotantă, cu diferite precizii:
o FLOAT reprezentat pe 4 octeți;
o REAL reprezentat pe 8 octeți;
o DOUBLE [PRECISION] reprezentat pe 8 octeți;
 numere zecimale reprezentate cu precizia dorită:
o tipul NUMERIC sau DECIMAL, cu forma numeric[(p,s)], unde p este numărul total
de cifre afișate, iar s este numărul de cifre după punc tul zecimal.

• Tipul șir de caractere
o CHARACTER (n) sau CHAR (n) definesc șiruri de caractere cu lungime fixă;
o CHARACTER VARYING sau VARCHAR (n) defineșt e șirul de caractere cu
lungime variabilă.

Asemănarea dintre cele două tipuri prezentate mai sus este aceea că ambele reprezintă șiruri de
maxim n caractere, iar deosebirea este aceea că pentru șiruri cu număr de caractere mai mic ca n,
CHAR (n) completează șirul cu spații albe până la n caractere, iar VARCHAR (n) memorează
numai atâtea caractere câte are șirul dat.

• Tipul șiruri de biți
o BIT(n) definește secvențe de cifre binare (care pot lua valoarea 0 sau 1) de lungime
finită n;

76
o BIT VARYING (n) definește secvențe de lungime variabilă, cu limita maximă n.

• Tipuri pentru data calendaristică și timp
o DATE permite memorarea datelor calendaristice în formatul yyyy -mm-dd;
o TIME permite memorarea timpului, folosind trei câmpuri hh:mm:ss;
o TIMESTAMP(p) permite memorarea combinată a datei calendaristice și a timpului,
cu precizia p pentru câmpul SECOND (al secundel or); valoarea implicită a lui p este
6;
o INTERVAL este utilizat pentru memorarea intervalelor de timp.

Tipurile de date sunt „case -insensitive”, deci nu țin cont de caracterele mari sau mici.

4.5. Categorii de instrucțiuni SQL

În funcție de tipul acțiunii pe care o realizează, instrucțiunile SQL se împart în mai multe categorii.

Datorită importanței pe care o au comenzile componente, unele dintre aceste categorii sunt evidențiate
ca limbaje relaționale în cadrul SQL, și anume:
• limbajul de definire a datelor (LDD sau DDL – Data Definition Language);
• limbajul de interogare a datelor (LQD sau DQL – Data Query Language);
• limbajul de prelucrare a datelor (LMD sau DML – Data Manipulation Language);
• limbajul de control al datelor (LCD sau DCL – Data Control Language).

Pe lângă comenzile care alcătuiesc aceste limbaje, SQL cuprinde:
• instrucțiuni pentru controlul sesiunii;
• instrucțiuni pentru controlul sistemului;
• instrucțiuni SQL încapsulate.

5. Limbajul de definire a datelor (LDD)
Limbajele relaționale de definire a datelor oferă următoarele facilități utilizatorilor:
• facilități de descriere a datelor la nivel conceptual . În vederea descrierii datelor la nivel
conceptual, limbajele relaționale conțin o serie de comenzi, și anume:
o crearea unei BD (dicționarul BD): CREATE DATABASE;
o ștergerea unei BD: DROP DATABASE;
o crearea tabelelor de bază: CREATE TABLE;
o ștergerea tabelelor de bază: DROP TABLE;
o crearea de sinonime: CREATE SYNONYM;
o ștergerea sinonimelor: DROP SYNONYM;
o actualizarea structurii unei tabele: ALTER TABLE cu opțiunile ADD, MODIFY,
DROP;
o adăugarea restricțiilor de integritate: ASSERT ON. În Oracle restricțiile de integritate
sunt: NULL, CHECK, pe cheie (PRIMARY, UNIQUE, REFERENTIAL).

• facilități de descriere a datelor la nivel logic . Pentru descrierea datelor la nivel logic,
limbajele relaționale dispun de o serie de comenzi, precum:
o crearea tabelelor virtuale: CREATE VIEW;
o ștergerea tabelelor virtuale: DROP VIEW;
o acordarea drepturilor de acces la BD:
 GRANT CONNECT – conectarea la BD a unui utilizator;

77
 GRANT drepturi – acordarea unor drepturi de acces (pentru regăsire,
actualizare etc.).
o retragerea drepturilor de acces la BD:
 REVOKE drepturi – retragerea unor drepturi;
 REVOKE CONNECT – deconectarea unui utilizator de la B D.

 facilități de descriere a datelor la nivel fizic . Pentru definirea unor caracteristici legate de
organizarea la nivel fizic a datelor din baza de date, limbajele relaționale dispun de o serie
de comenzi, și anume:
o crearea indecșilor: CREATE INDEX;
o ștergerea indecșilor: DROP INDEX;
o controlul alocării spațiului fizic al BD:
 CREATE SPACE – creează un model de alocare a spațiului fizic pentru o
BD;
 ALTER SPACE – actualizează modelul de alocare a spațiului fizic;
 DROP SPACE – șterge un model de alocare a spațiului fizic.
o regruparea fizică a datelor dintr -o BD (clustere):
 CREATE CLUSTER – creează un cluster dintr -o BD;
 ALTER CLUSTER – actualizează un cluster;
 DROP CLUSTER – șterge un cluster.

5.1. Comenzi (CREATE, ALTER, DROP)

Limbajul de definire a datelor (a schemei unei BD) include instrucțiuni ce permit:
 crearea schemei bazei de date;
 adăugarea relațiilor la schema bazei;
 ștergerea unor relații existente;
 adăugarea de noi atribute relațiilor existente;
 optimizarea bazei de date (index, grup, decla nșator);
 definirea structurii fizice și logice a unei BD;
 restricții cu privire la utilizarea structurii.

Comenzi pentru crearea unei baze de date
CREATE DATABASE nume_baza;

Comenzi pentru suprimarea unei baze de date
DROP DATABASE nume_baza;
Această comandă distruge BD cu numele nume_baza .

Comenzi pentru crearea relațiilor de bază
În cadrul acestor comenzi se precizează numele relației , precum și numele și tipul atributelor.
CREATE TABLE nume_tabela (atribute);

Crearea unei relații indicând cheia la nivel de coloană
Exemplu : Să se creeze relația PRODUSE(cod_produs , denumire_proddus ,
unitate_de_masura , data_expirarii ).
CREATE TABLE PRODUSE
(cod_produs VARCHAR(5 ) PRIMARY KEY ,
denumire_proddus VARCHAR(30) ,
unitate_de_masura VARCHAR(2) ,

78
data_expirarii DATE);

Crearea unei relații indicând cheile la nivel de tabel
Exemplu : Să se creeze relația LINII_FACTURI( nr_factura , cod_produs , cantitate ,
PRET_UNIRAR ).
CREATE TABLE LINII_FACTURI
(nr_factura VARCHAR(5 ),
cod_produs CHAR (5),
cantitate REAL,
pret_unitar REAL,
PRIMARY KEY (nr_factura , cod_produs ),
FOREIGN KEY (cod_produs )
REFERENCES PRODUSE (cod_produs ));
Dacă cheia primară are mai mult de o coloană atunci cheile trebuie indicate la nivel de tabel.

Crearea unui tabel prin copiere
Exemplu : Să se creeze relația PRODUSE_PAINE( cod_produs , denumire_proddus ,
unitate_de_masura , data_expirarii ), utilizând copierea datelor din relația PRODUSE .
CREATE TABLE PRODUSE_PAINE
SELECT cod_produs , denumire_proddus , unitate_de_masura ,
data_expirarii
FROM PRODUSE
WHERE denumire_proddus LIKE 'PAINE';

Comenzi pentru suprimarea unei relații de bază
DROP TABLE nume_tabela;
Comanda SQL distruge relația nume_tabela.

Comenzi pentru schimbarea numelui unei relații
RENAME nume_tabela TO nume_tabela_nou;
Exemplu : Să se modifice numele relației PRODUSE_PAINE în PROD_PAINE , apoi să se suprime
relația PROD_PAINE .
RENAME TABLE PRODUSE_PAINE TO PROD_PAINE ;
DROP TABLE PROD_PAINE ;

Comenzi pentru modificarea structurii unei relații
Prin modificarea structurii unei relații se înțelege:
• extinderea schemei relației prin adăugarea de noi atribute;
• restrângerea schemei unei relații prin suprimarea unor atribute;
• modificarea numelui și/sau tipului unui atribut din cadrul relației.

Unele limbaje relaționale (QBE) admit toate aceste tipuri de modificări în schema unei relații, iar
altele (SQL sau QUEL) numai o parte.
ALTER TABLE nume_tabel …

Adăugarea unui atribut cu ajutorul opțiunii ADD
Exemplu : Să se adauge atributul „ TOTAL_VALOARE_FACTURA ” la relația LINII_FACTURI.
ALTER TABLE LINII_FACTURI
ADD (TOTAL_VALOARE_FACTURA REAL);

Modificarea unui atribut cu ajutorul opțiunii MODIFY

79
Exemplu : Să se modifice forma prețului unitar din relația LINII_FACTURI .

ALTER TABLE LINII_FACTURI
MODIFY pret_unitar DECIMAL (10,2);

Comenzi pentru declararea restricțiilor de integritate (a constrângerilor)
Constrângerea este un mecanism care asigură că valorile unei coloane sau a unei mulțimi de coloane
satisfac o condiție declarată. Unei constrângeri i se poate da un nume unic. Dacă nu se specifică un
nume explicit atunci sistemul automat îi atribuie un nume de forma SYS_Cn , unde n reprezintă
numărul constrângerii. Constrângerile pot fi șterse, pot fi adăugate, pot fi activate sau dezactivate, dar
nu pot fi modificate.
Prin comanda CR EATE TABLE pot fi specificate anumite restricții (constrângeri) prin care se
exprimă o condiție care trebuie respectată de toate tuplurile uneia sau mai multor relații. Acestea pot fi
definite cu ajutorul comenzii :
ALTER TABLE

Constrângerile declarative pot fi:
• constrângeri de domeniu , care definesc valorile luate de un atribut:
o DEFAULT
o NOT NULL
o UNIQUE
o CHECK
• constrângeri de integritate a entității , care precizează cheia primară :
o PRIMARY KEY
• constrângeri de integritate referențială , care asigură corespondența între cheile primare și
cheile externe corespunzătoare :
o FOREIGN KEY

Fiecărei restricții i se poate da un nume, lucru util atunci când, la un moment dat (salvări, restaurări,
încărcarea BD) se dorește dezactivarea uneia sau mai multora dintre acestea. Astfel se prefigurează
numele fiecărei restricții cu tipul său:
• pk_(PRIMARY KEY) – pentru cheile primare;
• un_(UNIQUE) – pentru cheile alternative – care impune respectarea unicității valorilor ;
• nn_(NOT NULL) – pentru atributele obli gatorii;
• ck_(CHECK) – pentru reguli de validare la nivel de atribut;
• fk_(FOREIGN KEY) – pentru cheile străine.

Exemplu : Să se realizeze constrângerea de cheie primară, de cheie externă și constrângerea de
domeniu pentru relația LINII_FACTURI .
CREATE TABLE LINII_FACTURI
(nr_factura VARCHAR(5) NOT NULL ,
CONSTRAINT pk_nr PRIMARY KEY (nr_factura ),
cod_produs CHAR (5),
CONSTRAINT FOREIGN KEY fk_co(cod_produs )
REFERENCES PRODUSE ( cod_produs ),
cantitate REAL,
PRET_UNIRAR REAL);

Observații:
• Liniile ce nu respectă constrângerea sunt depuse automat într -un tabel special;
• Constrângerile previn ștergerea unui tabel dacă există dependențe;

80
• Constrângerile pot fi activate sau dezactivate în funcție de necesități.
• Constrângerile pot fi create o dată cu tabelul sau după ce acesta a fost creat.

Modificarea unei restricții de integritate
ALTER TABLE nume_tabela
MODIFY(nume_atribut TIP_CONSTRÂNGERE);

Exemplu : Să se modifice una din constrâ ngerile din exemplul de mai sus:
ALTER TABLE LINII_FACTURI
MODIFY PRET_UNIRAR REAL NOT NULL ;

Activarea și/sau dezactivarea unei constrângeri
Activarea sau dezactivarea unei constrângeri se realizează cu ajutorul opțiunilor ENABLE sau
DISABLE .

Exemplu : Să se dezactiveze, apoi să se activeze constrângerea de cheie primară din relația
LINII_FACTURI .
ALTER TABLE LINII_FACTURI
ADD (CONSTRAINT pk_nr PRIMARY KEY (nr_factura ) DISABLE);
ALTER TABLE LINII_FACTURI ENABLE (CONSTRAINT pk_nr);

Suprimarea unei constrângeri cu ajutorul opțiunii DROP
ALTER TABLE nume_tabela DROP PRIMARY KEY;

Exemplu : Să se suprime restricția de cheie primară (pentru atributul „ nr_factura ”) din tabela
LINII_FACTURI .
ALTER TABLE LINII_FACTURI DROP PRIMARY KEY;

Adăugarea unei constrângeri cu ajutorul opțiunii ADD
ALTER TABLE nume_tabela ADD CONSTRAINT …;

Exemplu : Să se adauge restricția de cheie primară „nr_factura” pentru relația LINII_ FACTURI.
ALTER TABLE LINII_FACTURI
ADD CONSTRAINT pk_nr PRIMARY KEY (nr_factura );

Observații:
• Comanda ALTER TABLE realizează modificarea structurii tabelului (la nivel de coloană
sau la nivel de tabel), dar nu modificarea conținutului acestuia;
• Constrângerile pot fi adăugate (ADD CONSTRAINT), șterse (DROP CONSTRAINT),
activate (ENABLE) sau dezactivate (DISABLE), dar nu pot fi modificate;
• Dacă există o cheie externă care referă o cheie primară și dacă se încearcă ștergerea cheii
primare, această ștergere nu se poate realiza (tabelele sunt legate prin declarația de cheie
externă). Ștergerea este totuși permisă dacă în comanda ALTER apare opțiunea
CASCADE, care determină și ștergerea cheilor externe ce referă cheia primară urmărind
sintaxa :
ALTER TABLE Nume_tabela
DROP PRIMARY KEY CASACDE ;

81
6. Limbajul de manipulare a datelor (LMD)

Instrucțiunile LMD (sau DML, Data Manipulation Language) sunt utile pentru interogarea și
prelucrarea datelor din obiectele unei scheme. Aceste instrucțiuni permit:
• interogarea bazei de date (SELECT);
• adăugarea de î nregistrări în tabele (sub forma rândurilor din tabele) sau vizualizări
(INSERT);
• modificarea valorilor unor coloane din înregistrările existente în tabele sau vizualizări
(UPDATE);
• suprimarea de înregistrări din tabele sau vizualizări (DELETE).

O colecți e de comenzi LMD care formează o unitate logică de lucru se numește tranzacț ie.

Instrucțiunile LMD individuale afectează datele dintr -un singur tabel. Este posibil ca într -o
instrucțiune LMD să se refere și o vizualizare care conține date din mai multe t abele (adică o
vizualizare care conține o uniune de tabele), dar, în acest caz, instrucțiunea LMD poate referi numai
coloane dintr -un singur tabel al vizualizării. Cu alte cuvinte, atunci c ând o instrucțiune LMD folosește
o vizualizare, toate coloanele viz ualizării referite în instrucțiunea LMD trebuie să corespundă unor
coloane dintr -un singur tabel fizic al bazei de date.

Sistemul SGBD nu va efectua în baza de date nici o modificare care încalcă una din restricții.

La formarea instrucț iunilor LMD , trebuie să se țină seama de urmă toarele aspecte referitoare la
restricțiile tabelului modificat:
• restricții de tip cheie primară . Atunci când se inserează un nou rând într -un tabel, cheia
primară a noului rând trebuie să fie unică în întregul tabel. Când se modific ă valoarea unei
chei primare (ceea ce se întâmplă rareori), noua valoare trebuie să fie unică în întregul
tabel;
• restricții de unicitate . Ca și în cazul cheilor primare, coloanele pe care a fost definită o
restricție de unicitate trebuie sa aibă valori unice în întregul tabel;
• restricții NOT NULL . O valoare nula ( NULL ) este o modalitate specială prin care sistemul
SGBD tratează valoarea unei c oloane pentru a indica faptul că valoarea coloanei
respective nu este cunoscută. O valoare nul ă nu este ac elași lucru cu un spațiu liber, un ș ir
vid sau valoare a zero – este o valoare specială care nu este egală cu nimic altceva.
În cazul instrucțiunilor INSERT, trebuie specificate valori pentru toate coloanele cu
restricții NOT NULL.
În cazul instruc țiunilor UPDATE nu se pot î nlocui valorile unei coloane cu valori nule
dacă pe coloana respectivă este definită o restricție NOT NULL.
Dacă instrucțiunea LMD referă o vizualizare, nu poate fi folosită într-o instrucțiune
INSERT dacă una dintre coloanele tabelului c u o restricție NOT NULL (obligatorii)
lipsește din definirea vizualizării;
• restricții referențiale . Nu se poate insera sau actualiza valoarea unei chei externe decât
dacă există deja rândul părinte corespondent care conține valoarea cheii în coloana cheii
primare. În sens invers, nu se poate șterge un rând părinte dacă există rânduri subordonate
care referă valoarea din rândul părinte, decât dacă restricția a fost definită cu opțiunea
ON DELETE CASCADE . În general inserările în tabele trebuie fă cute ierarhic (mai întâ i
rândurile părinte, apoi râ ndurile copii), iar ștergerile trebuie făcute în ordine inver să
(copiii înaintea părinților);

82
• restrictii de verificare (CHECK). O instructiune INSERT sau UPDA TE nu poate stoca
într-o coloană o valoare care înca lcă o restricție CHECK definită pentru coloana
respectivă.

Actualizarea datelor se referă la adăugarea unor noi rânduri într -o tabelă (cu instrucțiunea INSERT), la
modificarea valorilor uneia sau mai multor valori dintr -un rând (cu comanda UPDATE) și la ș tergerea
unui rând dintr -o tabelă (cu comanda DELETE).

6.1. Interogarea datelor (Comanda SELECT)

Comanda fundamentală a standardului SQL este SELECT, aceasta permițând interogarea unei baze de
date.

Componentele interogării se numesc clause .

Sintaxa ge nerală a comenzii SELECT este următoarea:
SELECT [ALL/DISTINCT/UNIQUE] listă de selecție
FROM listă de relații (tabele)
WHERE condiție de căutare asupra liniilor
GROUP BY listă de atribute care permit partiționarea
HAVING condiție asupra partițiilor
ORDER BY listă de atribute;

Clauzele SELECT și FROM sunt obligatorii. SELECT specifică datele care se selectează, iar clauza
FROM specifică relațiile din care se selectează. Restul clauzelor sunt opționale.

Exemplul 1 : Să se selecteze toate produsele împreu nă cu toate datele acestora existente în baza de
date.
SELECT * FROM PRODUSE;

Exemplul 2 : Să se selecteze toate produsele care expiră în data de 2017 -07-03.
SELECT * FROM PRODUSE
WHERE data_expirarii =’2017-07-03’;

Interogarea datelor folosind operatorii IS și IS NOT
Exemplu : Să se selecteze numele tuturor clienților care au completată adresa, apoi să se afișeze
numele tuturor persoanelor care nu au numărul de telefon completat.
SELECT nume_client FROM CLIENTI
WHERE adresa_client IS NOT NULL;

SELECT nume_client FROM CLIENTI
WHERE telefon_client IS NULL;

Interogarea datelor folosind operatorii logici AND, OR, NOT
Sintaxa pentru interogarea care utilizează un operator logic este :
condiție1 AND condiție 2;
condiție1 OR condiție 2;
NOT condiție;

Exemplu : Să se determine numărul facturii și codul produselor pentru produsele cu o cantitate mai
mare de 300 și cu un preț unitar mai mare sau egal ca 100 .

83
SELECT cod_produs , nr_factura FROM LINII_FACTURI
WHERE cantitate >’300’ AND pret_unitar >=’100’;

Interogarea datelor folosind operatorul IN
SELECT valoare_câmp IN (valoare1, valoare2,…);

Această sintaxă a operatorului IN este similară cu următoarea listă de disjuncții:
Valoare_câmp=valoare1 OR valoare_câmp=valoare2 OR …;

Exemplu: Să se selectez e numărul facturii și codul produselor pentru produsele cu prețul unitar de 70,
80, 90.
SELECT * FROM LINII_FACTURI
WHERE pret_unitar IN (70.00,80.00,90.00);

Inter ogarea datelor folosind DISTINCT
Pentru a selecta seturi de valori distincte, adică eliminarea valorilor duplicat, în SQL se folosește
sintaxa DISTINCT, micșorând astfel setul de date.

Sintaxa acestei comenzi este:
SELECT DISTINCT nume_câmp1, nume_câmp2,…
FROM nume_tabela
WHERE comenzi;
sau
SELECT DISTINCT *
FROM nume_tabela;

Sintaxa DISTINCT se referă la o înregistrare care poate cuprinde unul sau mai multe câmpuri.

Exemplu : Să se afișeze toate datele distincte despre produse .
SELECT DISTINCT denumire_produs FROM PRODUSE;

Interogarea datelor folosind operatorul LIKE
Se cunosc mai multe modalități de utilizare a expresiei LIKE, și anume:
• pentru o expresie care începe cu o anumită literă, de exemplu litera ‘A’: LIKE ‘A%’;
• pentru o expresie care se termină cu o anumită literă, de exemplu litera ‘A’: LIKE ‘%A’;
• pentru o expresie care include o anumită literă, de exemplu litera ‘A’: LIKE ‘%A%’;

Exemplu : Să se selecteze numele adresa și emailul tuturor persoanelor din București care au adresă de
email pe gmail sau personal .
SELECT nume_client , adresa_client , email_client
FROM CLIENTI
WHERE adresa_client LIKE ‘%BUCUREȘTI %’ AND
(email_client LIKE ‘%gmail%’ OR email_client LIKE’%personal%’);

Interogarea datelor folosind operatorul BETWEEN
Operatorul se utilizează în combinație cu două valori între care se află valoarea la care se referă
operatorul.

Sintaxa este:
val BETWEEN minim AND maxim;

84
sau
val>=min AND val<=max;

Cele trei expresii val, min, max pot fi de tip numeric (numeric, decimal, int, smalint etc.) sau de tip
dată calendaristică.

Exemplu : Să se selecteze codurile tuturor facturilor înregist rate în perioada 1 ianuarie 2017 și 1 mai
2017 .
SELECT nr_factura
FROM FACTURI
WHERE data_factura BETWEEN ‘2017-01-01’ AND ‘2017-05-01’;

Interogarea datelor folosind funcțiile calendaristice YEAR, DAY, MONTH
Funcțiile YEAR, DAY, MONTH rețin dintr -un câmp de tip dată calendaristică anul, ziua, respectiv luna.

Exemplu : Să se vizualizeze codurile tuturor facturilor înregistrate în luna mai.
SELECT nr_factura
FROM FACTURI
WHERE MONTH(data_factura )=05;

Interogarea datelor folosind ordonarea
Datele se pot ordona după orice câmp. Ordonarea se poate face atât crescător cât și descrescător.

Sintaxa pentru interogarea
• ordonată crescător este :
ORDER BY nume_câmp (ASC);
• ordonată descrescător este :
ORDER BY nume_câmp (DESC);

Dacă ORDER BY nu este urmat de ASC sau DESC, ordonarea se face implicit crescător.

Exemplu : Să se vizualizeze lista clienților în ordine alfabetică.
SELECT nume_client
FROM CLIENTI
ORDER BY nume_client ;

Interogarea datelor din mai multe tabele
Atunci când este necesară obținerea de informații din mai multe tabele se utilizează condiții de join. În
acest fel liniile dintr -un tabel pot fi puse în legătură cu cele din alt tabel conform valorilor comune ale
unor coloane. Interogarea datelor din mai multe relații este strâns legată de noțiunea de cheie primară,
cheie secundară, restricții de integritate, asocieri între relații.

Exemplu : Să se afișeze facturile, numele și adresa clienților corespunzătoare facturilor .
SELECT FACTURI.nr_factura , CLIENTI.nume_client ,
CLIENTI.adresa_client
FROM FACTURI, CLIENTI
WHERE FACTURI.nume_client = CLIENTI.nume_client ;

Observații:

85
• Atunci când în clauza FROM a unei comenzi SELECT apar mai multe tabele se realizează
produsul cartezian al acestora. De aceea numărul de linii rezultat crește considerabil, fiind
necesară restricționarea acestora cu o clauza WHERE. Se utilizează sintaxa:
nume_tab el.nume_câmp
Clauza FROM specifică două relații (FACTURI și CLIENTI ).
Clauza SELECT cuprinde valori din relația FACTURI și din relația CLIENTI , prin
urmare trebuie definite câmpurile în funcție de tabela din care fac parte.
• Clauza WHERE include condiții care exprimă o egalitate între valorile identificatorului
nume_câmp a relației nume_tabel și a celei ale referinței la acest identificator în tabela
referită.

Tipuri de asocieri pentru relații
Rolul unei relații este de a modela entități, între relații ex istă aceleași tipuri de asocieri ca și între
entități, și anume asocieri una-la-una, una -la-multe, multe -la-multe .

• Asocieri de la una-la-una
Două relații stochează informații în asocierea una-la-una dacă unei înregistrări din relația A îi
corespunde (sau nu) o singură înregistrare din B.
Acest tip de asociere este utilizată mai rar. Există, totuși, cazuri în care este necesară și utilă stabilirea
unei astfel de relații.

Exemplu:

Asociere de tip 1:1

• Asocieri de la una-la-multe
O relație A se află într -o asociere de una-la-multe cu o relație B dacă fiecărei înregistrări din A îi
corespund una sau mai multe înregistrări din relația B. Unei înregistrări din relația B nu îi corespunde
decât maxim o înregistrare din relația A.

Sunt utilizate următoarele denumiri:
• B este relația copi l, sau relația care referă la A , sau relația cheie străină;
• A este relația părinte (master) , sau relația referită , sau relația cheie primară.

Exemplu:

Asociere de tip 1:M LINII_FACTURI

nr_factura#
cod_produs#
cantitate
pret_unitar
PRODUSE

cod_produs#
denumire_produs
unitate_de_masura
data_expirarii

FACTURI

nr_factura#
data_factura
nume_client
delegat

LINII_FACTURI

nr_factura#
cod_produs#
cantitate
pret_unitar

86

Observație : Relația A are cheia primară „ nr_factura ”, iar relația B are atributul „ cod_produs ” cheie
externă.

• Asocieri de la mai multe -la-multe
O relație A se află în asociere de tipul multe -la-multe cu o relație B dacă unei înregistrări din relația A
îi pot corespunde mai multe înregistrări din relația B și unei înregistrări din relația B îi pot corespunde
mai multe înregistrări din relația A.

O asociere N la M nu se definește direct, asocierea construindu -se cu ajutorul unei relații de joncțiune.
În această relație se păstrează legătura între cele două relații, precum și informațiile necesare.

Exemplu:
A 1:M B 1:M C

Asociere de tip N:M

Observație : În exemplul de mai sus, relația LINII_FACTURI realizează joncțiunea între relațiile
FACTURI și PRODUSE , stocând informațiile privind nr_factura, cod_produs, cantitate, pret_unitar
etc.

Astfel, asocierea N la M este vizualizată sub forma a două relații de 1 la M .

Interogarea datelor din mai multe relații folosind aliasuri (sau pseudonime)
Un alias este o redenumire fie a unui câmp, fie a unei relații. Aliasurile sunt utilizate la eliminarea
rescrierii complete a denumirii unei relații sau a unui câmp, redenumindu -le într -un mod simplificat.

Sintaxa utilizată este:
nume_relație/camp AS nume_nou;
sau
nume_relație/camp nume_nou;

Există posibilitatea de a utiliza aliasuri pentru tabelele din clauza FROM și utilizarea lor în cadrul
comenzii SELECT respective (alias.coloana). Această identificare (prin 'tabel.coloana' sau
'alias.coloana') este obligato rie atunci cân d se face referință la o coloană ce apare în mai mult de un
tabel din clauza FROM.

Exemplu : Să se afișeze facturile, numele și adresa clienților corespunzătoare facturilor, folosind
aliasuri.
SELECT F.nr_factura , C.nume_client , C.adresa_client
FROM FACTURI F, CLIENTI C
WHERE F.nume_client = C.nume_client ;
FACTURI

nr_factura#
data_factura
nume_client
delegat

LINII_FACTURI

nr_factura#
cod_produs#
cantitate
pret_unitar
PRODUSE

cod_produs#
denumire_produs
unitate_de_masura
data_expirarii

87
Observație : În cazul în care un atribut apare doar într -o relație dintre cele menționate în listă, nu este
obligatorie precizarea relației (adică a aliasului) din care face par te atributul respectiv .

Interogarea datelor din mai multe relații folosind tipuri de asocieri
Tipurile de asocieri utilizate în interogarea mai multor relații sunt:
1) INNER JOIN (joncțiunea internă);
2) LEFT OUTER JOIN (semijoncțiunea la stânga);
3) RIGHT OUTER JOIN (semijoncțiunea la dreapta);

1) INNER JOIN (joncțiunea internă) . Sintaxa
SELECT …FROM tabel_A INNER JOIN tabel_B ON (condiții de join)

selectează toate informațiile din relațiile A și B care corespund condițiilor de asociere.

Exemplu : Selectați numărul facturii și denumirea produselor fiecărei facturi folosind operația de join,
apoi utilizând clauza WHERE.
SELECT F.nr_factura , P.cod_produs, P.denumire _produs
FROM LINII_FACTURI F INNER JOIN PRODUSE P
ON (F.cod_produs =P .cod_produs );

SELECT F.nr_factura, P.denumire_produs
FROM LINII_FACTURI F , PRODUSE P
WHERE F.cod_produs =P .cod_produs ;

Observații:
• Rezultatul este același. Valorile NULL vor fi ignorate;
• Sintaxei SELECT -FROM -INNER JOIN i se pot adăuga și alte condiții, neincluse în
condițiile de join, dacă acestea se referă la alte câmpuri decât cele care participă la join.

2) LEFT OUTER JOIN (semijoncțiunea la stânga). Sintaxa
SELECT …FROM tabel_A LEFT OUTER JOIN tabel_B
ON (condiții de join)

selectează toate informațiile din A, pe care le completează cu informații din B, în măsura în care
satisfac condițiile de join; acolo unde nu vor exista informații din B, acestea vor fi completate cu
NULL.

Exemplu : Selectați toate facturile . Dacă există informații despre aceste facturi , afișați și aceste
informații.
SELECT *
FROM LINII_FACTURI F LEFT OUTER JOIN PRODUSE P
ON (F.cod_produs =P.cod_produs);

Observație : Ordinea în care se scrie denumirea relației în sintaxa LEFT OUTER JOIN este foarte
importantă. Astfel, relația din stânga este relația primară, adică relația pentru care se dorește returnarea
tuturor informațiilor; relația din dreapta este relația secundară, adică informațiile din ea sunt necesare
doar în măsura în care se potrivesc condițiilor de asociere. Astfel se explică și denumirea de asociere
de la stânga spre exterior.

3) RIGHT OUTER JOIN (semijoncțiunea la dreapta). Sintaxa
SELECT …FROM tabel_A RIGHT OUTER JOIN tabel_B

88
ON (condiții de join)

selectează toate informațiile din B, pe care le completează cu informații din A, în măsura în care
satisfac condițiile de join; acolo unde nu vor exista informații din A, acestea vor fi completate cu
NULL.

Exemplu : Selectați toate facturile și produsele corespunzătoare, inclusiv facturile fără niciun produs .
SELECT *
FROM PRODUSE P RIGHT OUTER JOIN LINII_FACTURI F
ON (P.cod_produs =F.cod_produs);

Observație : Sintaxa RIGHT OUTER JOIN este utilizată mai rar; de obicei se utilizează sintaxa LEFT
OUTER JOIN.

Interogarea datelor din mai multe relații folosind instrucțiunea UNION
Sintaxa interogării datelor din mai multe relații folosind instrucțiunea UNION este :
SELECT Câmp 1, Câmp 2, …, Câmp n
FROM Tabel 1
UNION (ALL)
SELECT Câmp 1A, Câmp 2A,…, Câmp nA
FROM Tabel 2

și returnează înregistrări distincte dacă este folosită instrucțiunea UNION , și toate înregistrările dacă
se folosește UNION ALL. Astfel operatorul UNION elimină duplicatele, iar UNION ALL
vizualizează toate înregistrările, inclusiv duplicatele.

Pentru a utiliza această interogare, trebuie să se țină seama de două cerințe: domeniile Câmp 1A, Câmp
2A,…, Câmp nA și Câm p 1, Câmp 2, …, Câmp n trebuie să fie respectiv aceleași și, numărul de
câmpuri din fiecare interogare trebuie să coincidă.

Operatorul UNION se folosește atunci când între relații nu e xistă o asociere directă.

Interogarea datelor mai multor relații folosind operatorul de concatenare a două șiruri de caractere
Rolul operatorului de concatenare a două șiruri de caractere este de a uni două șiruri de caractere într –
unul singur. Este utili zat în toate SGBD -urile, cu mici modificări ale simbolului: în SQL se folosește
simbolul ‚+’, în Oracle simbolul ‚||’ etc.

Se pot concatena o constantă cu un câmp, sau două câmpuri. Câmpurile trebuie să fie de tip text.

Sintaxa pentru concatenarea a două câmpuri este
CONCAT(Câmp1, Câmp2)

sau inserând virgula, spațiu sau oricare marcaj de delimitare
CONCAT(Câmp1,’,’, Câmp2 )
sau
CONCAT(Câmp1,’ ’, Câmp2)
concatenează cele două constante într -una singură ’ Câmp1 Câmp2’.

Observație : Concatenarea prezintă dezavantajul afișării câmpurilor NULL .

Interogarea datelor folosind funcțiile totalizatoare:

89
• MAX
• MIN
• COUNT
• SUM
• AVG

• Interogarea datelor folosind funcția MAX
Sintaxa:
SELECT MAX (Nume_câmp) FROM Nume_tabela

returnează un număr egal cu valoarea maximă a câmpului Nume_câmp din relația Nume_tabela ,
valorile NULL fiind ignorate.

Exemplu : Selectați cea mai recentă factură din tabela FACTURI , fără a da un nume rezultatului, apoi
cu nume pentru câmpul rezultat.
SELECT MAX (data_factura) FROM FACTURI;

SELECT MAX (data_factura ) AS data_ultimei_înregistrari
FROM FACTURI;

• Interogarea datelor folosind funcția MIN
Funcția MIN este o funcție similară cu funcția MAX, cu ajutorul căreia se poate determina valoarea
cea mai mică dintr -un câmp.

Atât funcț ia MIN cât și funcția MAX se pot aplica doar pentru tipurile de date numeric sau dată
calendaristică.

• Interogarea datelor folosind funcția COUNT
Sintaxa :
• SELECT COUNT (*) FROM Nume_tabela – returnează un număr egal cu
numărul de înreg istrări ale tabelei Nume_tabela;
• SELECT COUNT (Nume_câmp) FROM Nume_tabela – returnează un număr
egal cu numărul de valori nenule ale câmpului Nume_câmp din tabela Nume_tabela. Sunt
ignorate valorile NULL ;
• SELECT COUNT (DISTINCT Nume_câmp) FROM Nume_tabela – returneaz ă
un număr egal cu numărul de valori distincte nenule ale câmpului Nume_câmp din tabela
Nume_tabela. Sunt ignorate valorile NULL .

Exemplu : Precizați numărul de produse .
SELECT COUNT (cod_produs ) AS nr_produselor FROM PRODUSE;

• Interogarea datelor folosind funcția SUM
Sintaxa :
• SELECT SUM (Nume_câmp) FROM Nume_tabela – returnează un număr egal
cu suma tuturor valorilor câmpului Nume_câmp din relația Nume_Tabe la. Sunt ignorate
valorile NULL ;
• SUM (DISTINCT Nume_câmp) FROM Nume_tabela – returnează un număr
egal cu suma valorilor distincte ale câmpului Nume_câmp din relația Nume_Tabela.

90
Funcția SUM se aplică acelor câmpuri care au domeniul de valori de tipul FLOAT, DECIMAL,
NUMERIC, INT etc. și nu are sens pentru câmpuri de tip text.

Exemplu : Precizați suma tutu ror încasărilor existente pe facturile emise.
SELECT SUM (DISTINCT total_valoare_factura) FROM FACTURI;

• Interogarea datelor folosind funcția AVG
Sintaxa :
AVG (nume_câmp) FROM Nume_tabela
returnează un număr egal cu media aritmetică a tuturor valorilor câmpului Nume_câmp din relația
Nume_tabela. Valorile NULL sunt ignorate.

Funcția AVG se utilizează doar pentru date de tip numeric: INT, FLOAT, NUMERIC.

Exemplu: Selectați media prețurilor produselor .
SELECT AVG (pret_unitar ) FROM LINII_FACTURI ;

Interogarea datelor folosind instrucțiunea GROUP BY
Prin instrucțiunea GROUP BY se grupează datele după fiecare produs în parte.

Exemplu : Selectați fiecare produs în parte grupându -le crescător și precizați cantitatea vândută din
fiecare tip.
SELECT P.denumire_produs , SUM(F.cantitate ) AS suma
FROM PRODUSE P , LINII_FACTURI F
WHERE F.cod_produs = P.cod_produs
GROUP BY P.cod_produs ;

Observație : Menționarea clauzelor SELECT, FROM, WHERE, GROUP BY, ORDER BY în această
ordine este obligatorie. Greșeala frecventă care duce la apariția unor mesaje de eroare este aceea a
introducerii unor câmpuri după care se grupează în clauza SELECT și neintroducerea lor în clauza
GROUP BY.

Funcțiile de agregare se pot folosi ca extensii ale clauzei GROUP BY:
• ROLLUP – permite instrucțiunii SELECT să calculeze niveluri de subtotal multiple peste
un grup de dimensiuni;
• CUBE – generează toate subtotalurile care pot fi calculate dintr -un CUBE pe dimensiunile
specificate;
• GROUPING;
• GROUPING SET.

Interogarea datelor folosind instrucțiunea HAVING
Instrucțiunea HAVING se utilizează numai în combinație cu instrucțiunea GROUP BY. Clauza
HAVING este utilizată când se dorește filtrarea datelor grupate conform unor criterii. Acest e criterii
presupun compararea unor valori obținute prin apelarea unor funcții totalizatoare. Aceste tipuri de
comparări presupun gruparea datelor. Din această cauză, HAVING cere obligatoriu clauza GROUP
BY.

Exemplu : Selectați produsele grupate după cod care au prețul unitar cuprins între 500 și 3000.
SELECT P.denumire_produs , P.cod_produs , F.pret_unitar
FROM PRODUSE P, LINII_FACTURI F

91
WHERE F.cod_produs = P.cod_produs
GROUP BY P.cod_produs
HAVING F.PRET_UNIRAR BETWEEN 500 AND 3000;

6.2. Adăugarea de noi tupluri (Comanda INSERT)

În vederea adăugă rii unor rânduri noi într-o tabelă sau într -o vizualizare se utilizează comanda
INSERT. Instrucțiunea are două forme de bază: una în care valorile coloanelor sunt specificate chiar în
instrucțiune și alta în care valorile sunt selectate dintr -un tabel sau o vizualizare, folosind o
subinterogare.

Inserarea unui singur rând de date folosind clauza Values
Instrucțiunea INSERT care folosește o clauză VALUES poate crea un singur rând la fiecare rulare,
deoarece valorile pentru rândul de date respectiv sunt specificate chiar în instrucțiune.

Sintaxa generală a instru cțiunii este următoarea:
INSERT INTO nume_tabel_sau_vizualizare[(lista_de_coloane)]
VALUES (lista_de_valori);
sau
INSERT INTO nume_tabel /nume_view [(col1[, col2[,…]])]
VALUES (expresia1[, expresia2[,…]]) / subcerere;
• expresia1, expresia2, … reprezintă expre sii a căror evaluare este atribuită coloanelor
precizate (se inserează o linie);
• subcerere, reprezintă o interogare (se inserează una sau mai multe linii).

Exemplu :
INSERT INTO PRODUSE (cod_produs, denumire_produs,
unitate_de_masura, data_expirarii )
VALUES (50, ‘PAINE’ , ‘KG’,’2017-05-15’);

Clauza VALUES specifică valorile ce vor fi introduse în tabel sau vizualizare. Pentru a insera mai
multe linii prin aceeași instrucțiune INSERT, în locul acestei clauze se va preciza o subcerere.
Dacă nu se mai cun oaște ordinea de declarare a coloanelor se folosește comanda DESCRIBE care va
afișa lista coloanelor definite pentru tabela respectivă, tipul și lungimea lor.

Se pot remarcă următoarele:
• lista de coloane este opțională, dar dacă este inclusă trebuie să fi e încadrată între paranteze
rotunde;
• dacă lista de coloane este omisă, trebuie specificată o valoare pentru fiecare coloană din
tabel, în ordinea în care sunt definite coloanele în tabel. Este bine ca întotdeauna să se
includă lista de coloane, deoarece omiterea acesteia face ca instrucțiunea INSERT să fie
dependentă de definiția tabelului. Dacă o coloană este modificată sau în tabel este
adăugată o nouă coloană, chiar și opțională, probabil instrucțiunea INSERT va eșua la
următoarea rulare;
• dacă lista de coloane este specificată, lista de valori trebuie să conțină o valoare pentru
fiecare coloană din listă, în aceeași ordine. Cu alte cuvinte, între lista de coloane și lista de
valori trebuie să existe o corespondență una-la-una. Orice coloană care lipsește d in listă
va primi o valoare nu lă, presupunund că valorile nul e sunt acceptate în coloana respectivă;

92
• în clauza VALUES, valorile de tip caracter și dată calendaristică trebuie incluse între
apostrofuri. Nu se recomandă includerea între apostrof uri a valorilor numerice, întrucât
aceasta ar determina conversii implicite la tipul NUMBER;
• pentru introduce rea de valori speciale în tabel pot fi utilizate funcții;
• adăugarea unei linii care va conține valori NULL se poate realiza în mod implicit, prin
omiterea numelui coloanei din lista de coloane. Exemplu :
INSERT INTO PRODUSE (cod_produs, denumire_produs ,
unitate_de_masura )
VALUES (50, ‘PAINE’ , ‘KG’);

sau explicit, prin specificarea în lista de valori a cuvântului cheie NULL sau a șirului vid (‘’)
în cazul șirurilor de caractere sau datelor calendaristice. Exemplu :
INSERT INTO PRODUSE (cod_produs, denumire_produs ,
unitate_de_masura, data_expirarii )
VALUES (50, ‘PAINE’ , ‘KG’, NULL);

Inserări masive folosind instrucțiunea SELECT internă
Așa cum se observă, este nevoie de foarte mult cod pentru a insera în tabel un singur rând de date
folosind o instrucțiune INSERT cu clauza VALUES.
O altă soluție, care po ate fi folosită pentru a insera rânduri multiple într -un tabel, este forma care
folosește o instrucțiune SELECT internă. Această formă este utilă și pentru stabilirea următoarei valori
disponibile pentru o cheie primară cu valori secvențiale. De asemenea, poate fi folosită atunci când se
creează un tabel temporar pentru testare, care va fi populat cu toate datele dintr -un alt tabel.

Sintaxa generală a instrucțiunii este:
INSERT INTO nume_tabel_sau_vizualizare[(lista_de_coloane)]
SELECT instructiune_select;

Se remarcă următoarele:
• lista de coloane este opțională, dar dacă este inclusă trebuie să fie încadrată între paranteze
rotunde;
• dacă lista de coloane este omisă, instrucțiunea SELECT internă trebuie să furnizeze o
valoare pentru fiecare coloană din tabel, în ordinea în care sunt definite coloanele în tabel.
Este bine ca întotdeauna să se includă lista de coloane, deoarece omiterea acesteia face ca
instrucțiunea INSERT să fie dependentă de definiția tabelului. Dacă o coloană este
modificată sau în tab el este adăugată o nouă coloană, chiar și opțională, probabil
instrucțiunea INSERT va eșua la următoarea rulare;
• dacă lista de coloane este specificată, instrucțiune a SELECT internă trebuie să furnizeze o
valoare pentru fiecare coloană din lista de valori, în aceeași ordine. Cu alte cuvinte, între
lista de coloane și setul de rezultate al instrucțiunii SELECT trebuie să existe o
corespondenț ă una-la-una. Orice coloană care lipsește din listă va primi o valoare nul ă,
presupunâ nd că valorile nule sunt accepta te în coloana respect ivă;
• cuvântul cheie NULL poate fi folosit în instrucțiunea SELECT pen tru specificarea unei
valori nu le pentru o coloană.

Inserarea unor valori specifice de tip dată calendaristică
Formatul DD-MON -YY este de obicei folosit pentru a ins era o valoare de tip dată calendaristică. Cu
acest format, secolul este implicit cel curent. Deoarece data conține de asemenea informații despre
timp, ora implicită este 00:00:00.

Dacă o dată calendaristică necesită specificarea altui secol sau oră, trebu ie folosită funcția TO_DATE .

93

Exemplu :
INSERT INTO PRODUSE (cod_produs, denumire_produs,
unitate_de_masura, data_expirarii)
VALUES (50, ‘PAINE’, ‘KG’, TO_DAT E(‘FEB-3 , 2017’, 'MON -DD,
YYYY’));

Inserare de valori folosind variabile de substituție

• Crearea unui script interactiv prin folosirea variabilelor de substituție SQL*Plus
Exemplu:
INSERT INTO PRODUSE (cod_produs, denumire_produs, unitate_de_masura)
VALUES (‘&cod_produs’, ‘&denumire_produs’, ‘&unitate_de_masura’)

• Crearea unui script pentru manipularea datelor
Comanda împreună cu variabilele de substituție pot fi salvate într -un fișier și acesta poate fi executat.
De fiecar e dată când se execută fișierul sunt cerute valori noi pentru variabile. Prin intermediul
comenzii SQL*Plus ACCEPT , mesaj ele afi șate la cererea introducerii valorilor pot fi modificate.
• ACCEPT – memoreaza valoarea într -o variabilă;
• PROMPT – afișează textul specificat.

Exemplu :
ACCEPT cod_produs PROMPT 'Introduceti codul produsului :'
ACCEPT denumire_produs PROMPT 'Introduceti denumirea produsului :'
INSERT INTO PRODUSE (cod_produs, denumire_produs)
VALUES (‘&cod_produs’, ‘&denumire_produs’)

Exemplul înregistrează informația pentru un produs, în tabelul PRODUSE . Utilizatorului îi sunt cerute
codul produsului și denumirea produsului , folosind mesajele de prompt stabilite în ACCEPT.

Parametrul de substitutie SQL*Plus nu trebuie precedat de & când este referit într -o comanda
ACC EPT. Pentru a continua o comandă SQL*PLUS pe linia următoare se folosește o linie ( -).

Copierea înregistrarilor dintr -un alt tabel
Comanda INSERT poate fi folosită pen tru a adă uga înregistrări într -un tabel, valorile pentru câmpuri
fiind extrase dintr -un tabel existent. Pentru aceasta se folosește, în locul clauzei VALUES, o
subinterogare (N u se mai foloseș te clauza VALUES).

Sintaxa este urm ătoarea:
INSERT INTO nume_tabel [ coloana ( , coloana ) ]
Subinterogare;

Numărul și tipul câmpurilor (coloanelor) din lista specificată în comanda INSERT trebuie să
corespundă numărului și tipului valorilor din subinterogare.

Exemp lu:
INSERT INTO PRODUSE (cod_produs, denumire_produs,
unitate_de_masura)
SELECT cod_produs, denumire_produs, unitate_de_masura
FROM PRODUSE
WHERE denumire_produs = 'PAINE';

94

Inserări multitabel
O inserare multitabel presupune introducerea de linii calculate pe baza rezultatelor unei subcereri, în
unul sau mai multe tabele. Acest tip de inserare, introdus de Oracle, este util în mediul data
warehouse. Astfel, datele extrase dintr -un sistem sursă, pot fi transformate ut ilizând instrucțiuni
INSERT multitabel, spre a fi încărcate în obiectele bazei de date.

Sintaxa clauzei inserare_multi_tabel este următoarea:
INSERT ALL INTO …[VALUES…] [INTO…[VALUES…] …]
| inserare_condiționată| subcerere

Clauza inserare_condiționată are forma următoare:
[ALL | FIRST]
WHEN condiție THEN INTO…[VALUES…]
[INTO…[VALUES…] …]
[WHEN condiție THEN INTO…[VALUES…]
[INTO…[VALUES…] …] …]
[ELSE INTO…[VALUES…]
[INTO…[VALUES…] …] …]

Pentru a efectua o inserare multitabel necondiționată, sistemul va exe cuta câte o instrucțiune
INSERT…INTO pentru fiecare linie returnată de subcerere.

Utilizând clauza inserare_condiționată, decizia inserării unei linii depinde de condiția specificată prin
intermediul opțiunii WHEN. Expresiile prezente în aceste condiții trebuie să facă referință la coloane
returnate de subcerere. O instrucțiune de inserare multitabel poate conține maxim 127 clauze WHEN.

Specificarea opțiunii ALL determină evaluarea tuturor condițiilor din clauzele WHEN. Pentru cele a
căror valoare este T RUE, se inserează înregistrarea specificată în opțiunea INTO corespunzătoare.

Opțiunea FIRST determină inserarea corespunzătoare primei clauze WHEN a cărei condiție este
evaluată TRUE. Toate celelalte clauze WHEN sunt ignorate.

Dacă nici o condiție din c lauzele WHEN nu este TRUE, atunci sistemul execută clauza INTO
corespunzătoare opțiunii ELSE, iar dacă aceasta nu există, nu efectuează nici o acțiune.

Inserările multitabel pot fi efectuate numai asupra tabelelor, nu și asupra vizualizărilor. De asemenea ,
acest tip de inserare nu se poate efectua asupra tabelelor distante. Subcererea dintr -o instrucțiune
corespunzătoare unei inserări multitabel nu poate utiliza o secvență.

6.3. Modificarea tuplurilor din tabele (Comanda UPDATE)

În funcție de momentul în care se dorește realizarea modificărilor asupra bazei de date, utilizatorul
poate folosi una din următoarele comenzi:
• SET AUTOCOMMIT IMM[EDIATE] (schimbările se efectuează imediat);
• SET AUTOCOMMIT OFF (schimbările sunt păstrate într-un buffer).

La execuția comenzii COMMIT se permanentizează schimbările efectuate, iar la execuția comenzii
ROLLBACK se renunță la schimbările realizate.

95
Instrucțiunea UPDATE este folosită pentru actualizarea datelor din coloanele unui tabel (sau al e unei
vizualizări).

Valorile câmpurilor care trebuie modificate pot fi furnizate explicit sau pot fi obținute în urma unei
cereri SQL.

Sintaxa generală a instrucțiunii UPDATE:
UPDATE nume_tabel_sau_vizualizare
SET nume_coloana = expresie[,nume_coloana = expresie…]
[ WHERE conditie];

Sau mai general, expresia poate fi o cerere:
UPDATE nume_tabel_sau_vizualizare
SET (coloana1[,coloana2[,…]]) = (subinterogare) / coloana =
exprresie /(interogare )
[WHERE conditie ]

Dacă este nevoie, se pot modifica mai multe înregistrări simultan.

Observații :
• Pentru a se putea executa instrucțiunea UPDATE, utilizatorul care o lansează în execuție
trebuie să aibă acest privilegiu;
• Dacă nu este specificată clauza WHERE se vor modifica toate liniile;
• Cererea trebuie să furnizeze un număr de valori corespunzător numărului de coloane din
paranteza care precede caracterul de egalitate.

Se remarcă următoarele:
• clauza SET conține o listă cu una sau mai multe coloane, împreu nă cu o expresie care
specifică noua valoare pentru fiecare coloană. Aceasta este o listă de perechi
(NUME,VALOARE) , separate prin virgule, cu un operator de egalitat e între fiecare
NUME și VALOARE ;
• expresia poate fi o constantă, un alt nume de coloană sa u orice altă expresie pe care SQL o
poate transforma într -o singură valoare, care poate fi apoi atribuită coloanei respective;
• clauza WHERE conține o expresie care limitează rândurile actualizate. Dacă această
clauză este omisă, motorul SQL va încerca să a ctualizeze toate rândurile din tabel sau din
vizualizare.

Oracle permite utilizarea valorii implicite DEFAULT în comenzile INSERT și UPDATE. Unei
coloane i se atribuie valoarea implicită definită la crearea sau modificarea structurii tabelului dacă:
• nu se precizează nici o valoare;
• dacă se precizează cuvântul cheie DEFAULT în comenzile INSERT sau UPDATE.

Dacă nu este definită nici o valoare implicită pentru coloana respectivă, sistemul îi atribuie valoarea
NULL . Cuvântul cheie DEFAULT nu poate fi specifi cat la actualizarea vizualizărilor.

Cazurile în care instrucțiunea UPDATE nu poate fi executată sunt similare celor în care eșuează
instrucțiunea INSERT.

Notă : În general, se folosește cheia primară pentru a identifica o singură înregistrare. Folosirea a ltor
coloane poate determina modificarea mai multor înregistrări.

96
De exemplu, identificarea unei singure înregistrări în tabelul PRODUSE prin denumire poate fi
periculoas ă, deoarece pot exista mai multe produse cu aceeași denumire .

Comanda UPDATE modifică anumite înregistrări dacă este specificată clauza WHERE.

Exemplul următor mărește prețul unitar cu 25% pentru produsul cu codul 50 .
UPDATE LINII_FACTURI
SET pret_unitar = pret_unitar * 1.25
WHERE cod_produs = 50;

Actualizarea înr egistrarilor folosind subinterogări după mai multe câmpuri
Exemplul următor mărește prețul unitar cu 15% pentru produsele care au preț ul unitar identic cu cel al
produsului cu codul 50.
UPDATE LINII_FACTURI
SET pret_unitar = pret_unitar * 1.15
WHERE pret_unitar IN
(SELECT pret_unitar
FROM LINII_FACTURI
WHERE cod_produs = 50);

Modificarea înregistrarilor folosind subinterogări după mai multe câmpuri (folosind valori dintr -un
alt tabel)
În clauza SET a unei comenzi UPDATE pot fi implementate subinterogări după mai multe câmpuri.
UPDATE nume_tabel
SET (coloana, coloana, … ) =
(SELECT coloana, coloana,
FROM nume_tabel
WHERE conditie )
WHERE conditie ;

Dacă se încercă atribuirea unei valori unui câmp care are legată de o constrângere de integritate, va
rezulta o eroare.

6.4. Ștergerea tuplurilor din tabele (Comanda DELETE)

Instrucțiunea DELETE șterge unul sau mai multe rânduri dintr -un tabel. Instrucțiunea poate să
folosească și o vizualizare, dar numai dacă aceasta se bazează pe un singur tabel (ceea ce înseamnă că
instrucțiunile DELETE nu pot fi folosite pentru vizualizări care conțin uniuni). În instrucțiunile
DELETE nu sunt referite niciodată coloane, doarece instrucțiunea șterge rânduri întregi de date,
inclusiv toate valorile datelor (toate coloanele) din r ândurile afectate. Dacă se șterge o singură valoare
din rândurile existente, se folosește instrucțiunea UPDATE pentru a înlocui valorile respective cu
valori nule (presupunând că valorile nule sunt per mise în acele coloane).

Sintaxa generală a instrucțiunii DELETE este:
DELETE FROM nume_tabel_sau_vizualizare [ AS alias]
[ WHERE conditie ];

Se remarcă următoarele:
• comanda DELETE nu șterge structura tabelului;

97
• clauza WHERE este opțională. Totuși, este folosită aproape întotdeauna, deoarece o
instrucțiune DELETE fără o clauză WHERE încearcă să șteargă toate rândurile din tabel.
În clauza WHERE pot fi folosite și subcereri;
• atunci când este inclusă, clauza WHERE specifică rândurile care urmează să fie ște rse.
Orice rând pentru care condiția WHERE este evaluată ca adevărată este șters din tabel;
• nu se pot șterge rânduri dacă se încalcă o restricție referențială. În general, rândurile
subordonate trebuie șterse înaintea rândurilor părinte;
• pentru a șterge li nii identificate cu ajutorul valorilor din alte tabele, se utilizează subcereri;
• comanda nu poate fi folosită pentru ștergerea valorilor unui câmp individual. Acest lucru
se poate realiza cu ajutorul comenzii UPDATE.

Dacă se încearcă ștergerea unei înregi strări care conține o valoare implicată într -o constrângere de
integritate, atunci va fi returnată o eroare.

În cazul în care constrângerea de integritate referențială a fost definită utilizând opțiunea ON DELETE
CASCADE, atunci instrucțiunea DELETE va șt erge atât liniile indicate, cât și liniile „copil“ din
tabelele corespunzătoare.

Ștergerile accidentale pot fi omise, restaurându -se valorile inițiale prin comanda AUTOCOMMIT
OFF.

Exemplu : Stergeți toate produsele care expiră înainte de 1 Ianuarie, 2017. Ștergerile să nu fie efectuate
imediat ci ulterior.
SET AUTOCOMMIT OFF
DELETE FROM PRODUSE
WHERE data_expirarii > TO_DATE('01 -01-17', 'DD -MM-YY');

Ștergerea înregistrărilor folosind valori dintr -un alt tabel
Pot fi folosi te subinterogări pentru a șterge înregistrări dintr -un tabel, folosind informațiile din altul.

Exemplul de mai jos șterge toate produsele cu denumirea PAINE . Subinterogarea caută în tabelul
PRODUSE codul produsului PAINE , apoi furnizează codul interogării principale, care șterge
înregistrări le din LINII_PRODUSE pe baza acestuia.
DELETE FROM LINII_PRODUSE
WHERE cod_produs =
(SELECT cod_produs
FROM PRODUSE
WHERE denumire_produs = 'PAINE ');

Încălcarea constrângerii de integritate
Dacă se încerarcă ștergerea unei înregistrări care conține un câmp cu o valoare legată de o
constrângere de integritate, se obține o eroare.

În exemplul de mai jos se încearcă ș tergerea produsului cu codul 10 din tabelul PRODUSE , dar
aceasta provoacă o eroare, deoarece codul produsului este folosit ca și cheie externă în tabelul
LINII_FACTURI .

Dacă înregistrarea părinte , care se încearcă să se șteargă, are înregistrări fii, atunci se primește un
mesaj de eroare: child record found violation ORA – 02292 .
DELETE FROM PRODUSE
WHERE cod_produs = 50;

98

7. Limbajul de control al datelor (LCD)

Limbajul pentru controlul datelor (LCD – Data Control Language ) include instrucțiuni SQL care
permit administratorilor să controleze accesul la datele din baza de date și folosirea diferitelor
privilegii ale sistemului DBMS, cum ar fi privilegiul de oprire și pornire a bazei de date.

Controlul unei baze de date cu a jutorul SQL -ului se referă la:
• asigurarea confidentialită ții și securității datelor;
• organizarea fizică a datelor;
• realizarea unor performanțe;
• reluarea unor acțiuni în cazul unei defecțiuni;
• garantarea coerenței datelor în cazul prelucrării concurente.

Sistemul de gestiune trebuie:
• să pună la dispoziția unui număr mare de utilizatori o mulțime coerentă de date;
• să garanteze coerența datelor în cazul manipulării simultane de către diferiți utilizatori.

Comenzile de control la dispozi ția administratorului (DBA) – GRANT, REVOKE – sunt utilizate pentru
a da sau a lua drepturi de acces (la comenzi L MD, deci la operarea unor modifică ri a bazei de date).

Sintaxa:
GRANT
[Privilegii]
ON
TO
Utilizator IDENTIFIED BY ’DenumireParola’;

REVOKE
[Privilegii]
ON
TO
Utilizator IDENTIFIED BY ’DenumireParola’;

7.1. Asigurarea confidențialității și securității datelor

Sistemul de gestiune trebuie să asigure securitatea fizică și logică a informației și să garanteze că
numai utilizatorii autorizați pot efectua operații corecte asupra bazei de date. Pentru acestea, e xistă
mecanisme care permit identificarea și autentificarea utilizatorilor și există proceduri de acces
autorizat care depind de date și de utilizato r, cum ar fi :
 conturi pentru utilizatori, cu parolă folosită pentru autentificare;
 grupuri, roluri, privilegiile și profilurile – acestea permit nu numai constrâ ngeri ci și stabilirea
unei politici de securitate. P entru a accesa un obiect, un utilizator trebuie să aibă privilegiile
necesare. Priv ilegiile pot fi acordate direct unui utilizator sau pot fi grupate în roluri, care la
rândul lor pot fi acordate utilizatorului .
Exemplu : Un forum de discuț ii are utilizatori grupaț i pe roluri ca: administrator, moderator,
membru. Fiecare rol poate avea privilegii diferite: administratorul poate configu ra baza de
date ( modifică schema, adaugă tabele, configurează interfața); moderatorul poate valida,
modifica, șterge postă rile membrilor; membrii pot adăuga înregistr ări.

99

7.2. Reluarea unor acțiuni în cazul unei defecțiuni

Reluarea unor acțiuni în cazul apariției unei defecțiuni hard sau soft presupune recuperarea ultimei
stări coerente a bazei de date. În funcție de defecțiunea care a determinat întreruperea lucrului,
restaurarea bazei de date se realizează automat de SGBD sau manual, adică necesită intervenție
umană.

Salvarea bazei de date se realizează conform unei strategii existând combinațiile:
 copii ale bazei de date și copii ale jurnalelor acestora;
 jurnale ale tranzacțiilor;
 jurnale ale imaginii înregistrărilor din baza de date.

Copiile bazei de date – pot fi realizate automat de sistem la anumite intervale de timp sau la comanda
administratorului bazei de date, ori de câte ori este nevoie și de obicei pe un alt suport magnetic decât
cele pe care rezidă baza de date. Aceste copii pot fi utilizate doar în situația în care prelucrările
efectuate între momentul realizării copiilor și cel al apariției unei defecțiuni pot fi reluate. Acest lucru
este posibil doar dacă prelucrările sunt efect uate într -o secvență cunoscută iar timpul necesar pentru
reprocesarea nu este foarte mare. Durata mare de execuție pentru astfel de copii face ca anumite
SGBD -uri să recurgă la copii ale jurnalelor bazei de date. Volumul datelor care vor fi copiate în aces t
caz va fi mai mic, iar procesul de restaurare va implica într -o măsură mai mică intervenția umană.

Jurnalul tranzacțiilor – este un fișier special întreținut de SGBD, în care sunt memorate informațiile
despre tranzacțiile efectuate asupra bazei de date , cum sunt:
 identificatorul sau codul tranzacției;
 momentul începerii execuției tranzacției;
 numărul terminalului sau identificatorul utilizatorului care a inițiat tranzacția;
 datele introduse;
 înregistrările modificate și tipul modificării.

Jurnalul imaginilor înregistrărilor din baza de date – se deosebește de jurnalul tranzacțiilor prin aceea
că el nu conține descrierea operațiilor efectuate asupra bazei de date, ci efectul acestora.

7.3. Garantarea coerenței datelor în cazul prelucrării concurente

Sistemul de gestiune a bazelor de date asigură accesul concurent al mai multor utilizatori la baza de
date. Fiecare utilizator trebuie să aibă o vedere validă ș i consistentă asupra bazei de date, incluzâ nd și
modifică rile făcute de alți utiliza tori; în acelaș i timp, procesarea incorectă a datelor trebuie evitată ,
pentru a nu afecta consistenț a datelor sau integritatea acestora.

În funcț ie de complexitatea operaț iei de acces concurent, problema gestion ării concurenț ei se
complică:
 acces concurent a mai multor utilizatori numai pentru consultarea datelor ;
 acces concurent a mai multor utilizatori cu unul dintre ei modificâ nd datele;
 acces concurent a mai multor utilizatori cu mai mulți dintre ei modificâ nd datele.

100
Pentru ultimele două , se utilizează blocarea datelor (primul utilizator care le accesează , le blochează ).
Cu câ t dimensiunea datelor blocate este mai mică , cu atât gestionarea accesului concurenț ial este mai
eficientă .

Coerența este asigurată cu ajutorul conceptului de tranz acție. Tranzacția este unitatea logică de lucru
constând din una sau mai multe instrucțiuni SQL, care trebuie să fie executate atomic (ori se execută
toate, ori nu se execută nici una!), asigurând astfel trecerea BD dintr -o stare coerentă în altă stare
coerentă. Dacă toate operațiile ce constituie tranzacția sunt executate și devin efective, spunem că
tranzacția este validată (COMMIT) , iar modificările (INSERT, DELETE, UPDATE) aduse de
tranzacție devin definitive (modificările sunt înregistrate și sunt vizibile tuturor utilizatorilor) . Din
acest punct prima instrucțiune SQL executabilă va genera automat începutul unei noi tranzacții.

Dacă dintr -un motiv sau altul (neverificarea condițiilor, accesul imposibil) o operație a tranzacției nu a
fost executată spunem că tranzacția a fost anulată (ROLLBACK) . Modificările aduse de toate
operațiile tranzacției anulate sunt și ele anulate și se revine la starea bazei de date de dinaintea
tranzacției anulate. Executarea unei instrucțiuni ROLLBACK presupune terminare a tranzacției curente
și începerea unei noi tranzacții.

Este posibil ca o tranzacție să fie descompusă în subtranzacții, astfel încât dacă este necesar să se
anuleze doar parțial unele operații.

Controlul tranzacțiilor constă în:
• definirea începutului și sfârșitului unei tranzacții;
• validarea sau anularea acesteia;
• o eventuală descompunere în subtranzacții.

Limbajul pentru controlul datelor (LCD) permite salvarea informației, realizarea fizică a modificărilor
în baza de date, rezolvarea unor probleme d e concurență.

Limbajul conține următoarele instrucțiuni:
• SET AUTO[COMMIT] {ON | OFF} – Dacă se folosește utilitarul SQL*Plus, există
posibilitatea ca după fiecare comandă LMD să aibă loc o permanentizare automată a datelor
(un COMMIT implicit).
Dacă est e setată pe ON, fiecare comandă LMD individuală duce la salvarea modificărilor,
imediat ce este executată. Nu se mai poate reveni la situația dinainte (un rollback nu mai este
posibil).
Dacă este setată pe OFF, COMMIT poate fi dată explicit. De asemeni, C OMMIT este
executată odată cu o comanda LDD sau la ieșirea din SQL*Plus;
• ROLLBACK [TO [SAVEPOINT] savepoint] – permite restaurarea unei stări
anterioare a bazei de date .
Dacă nu se specifică nici un SAVEPOINT, toate modificările făcute în tranzacția curen tă sunt
anulate, iar dacă se specifică un anumit savepoint, atunci doar modificările de la acel
savepoint până în m omentul respectiv sunt anulate;
• ROLLBACK TO SAVEPOINT name – șterge savepoint -ul și toate schimbările de după
el (temporare);
• SAVEPOINT name – folosită în conjuncție cu instrucțiunea ROLLBACK, pentru
definirea unor puncte de salvare în fluxul programului. Punctele de salvare pot fi
considerate ca niște etichete care referă o submulțime a schimbărilor dintr -o tranzacție,
marcând efectiv un punct de salvare pentru tranzacția curentă. În acest mod este posibilă
împărțirea tranzacției în subtranzacții.

101
Punctele de salvare nu sun t obiecte ale schemei, prin urmare, nu sunt referite în dicționarul
datelor. Server -ul Oracle implementează un punct de salvare implicit pe care îl mută
automat după ultima comandă LMD executată.
Dacă este creat un punct de salvare având același nume cu un ul creat anterior, cel definit
anterior este șters automat.
SAVEPOINT savepoint;

Starea datelor înainte de COMMIT sau ROLLBACK este următoarea:
• starea anterioară a datelor poate fi recuperată;
• utilizatorul curent poate vizualiza rezultatele operațiilor LMD prin interogări asupra
tabelelor;
• alți utilizatori nu pot vizualiza rezultatele comenzilor LMD făcute de utilizatorul curent
(read consistency);
• înregistrările (liniile) afectate sunt blocat e și, prin urmare, alți utilizatori nu pot face
schimbări în datele acestor înregistrări.

Execuția unei comenzi COMMIT implică anumite modificări:
• toate schimbările (INSERT, DELETE, UPDATE) din baza de date făcute după anterioara
comandă COMMIT sau ROLLBA CK sunt definitive. Comanda se referă numai la
schimbările făcute de utilizatorul care dă comanda COMMIT;
• toate punctele de salvare vor fi șterse;
• starea anterioară a datelor este pierdută definitiv;
• toți utilizatorii pot vizualiza rezultatele;
• blocările a supra liniilor afectate sunt eliberate; liniile pot fi folosite de alți utilizatori
pentru a face schimbări în date.

Execuția unei comenzi ROLLBACK implică anumite modificări:
• anulează tranzacția în curs și toate modificările de date făcute după ultima co mandă
COMMIT;
• sunt eliberate blocările liniilor implicate;
• nu șterge un tabel creat prin CREATE TABLE. Eliminarea tabelului se poate realiza doar
prin comanda DROP TABLE.

8. Exerciții de fixare a noțiunilor

1. Definiți următorii termeni:
– bază de date
– sistem de gestiune a bazei de date
– entitate
– relație
– atribut
– bază de date relațională

2. Descrieți pașii pentru construirea unei diagrame E -R.

3. Dați exemple de:
– relație de tip multe la multe
– relație de tip una la una
– relație de tip una la multe

102

4. Enunțați regulile lui Codd pentru SGBD -urile relaționale.

5. Descrieți componentele bazelor de date relaționale.

6. Explicați următoarele noțiuni:
– restricții de integritate
– cheia primară a unei relații
– cheia externă a unei relații

7. Precizați care sunt restricțiile de integritate minimală ale modelului relațional, apoi enunțați aceste
restricții.

8. Ce înseamnă dependență funcțională? Dați două exemple de DF.

9. Definiți următoarele noțiuni:
– atribut simplu (atomic)
– atribut compus

10. Când o re lație este în:
– forma normală 1?
– forma normală 2?
– forma normală 3? Dați exemple.

11. Descrieți elementele unei instrucțiuni.

12. Operatori SQL.

13. Tipuri de funcții scalare.

14. Exemple de funcții de grup.

15. Sintaxa generală a comenzii SELECT.

16. Sintaxa generală a instrucțiunii INSERT.

17. Sintaxa generală a instrucțiunii UPDATE.

18. Sintaxa generală a instrucțiunii DELETE.

19. Comenzile CREATE, ALTER, DROP.

20. Adăugarea/suprimarea unei constrângeri.

21. Activarea și/sau dezactivarea unei co nstrângeri.

22. Definiți noțiunea de tranzacție. În ce constă controlul tranzacțiilor?

103
23. Care sunt modificările pe care le implică execuția unei comenzi COMMIT? Dar ROLLBACK?

24. Asigurarea confidențialității și securității datelor.

25. Reluarea unor acțiuni în cazul unei defecțiuni.

26. Se dă următorul tabel:
STUDENT(id_student#, nume, prenume, data_nasterii, localitate )
Ce va determina execuția comenzii următoare?

SELECT id_student, nume, prenume
FROM student
WHERE localitate =’Craiova’
AND TO_CHAR(data_nasterii,’MM -YYYY’)=’07 -1993’;

27. Se dă următorul tabel:
TABLOU(id_tablou#, titlu, pictor, an_creare)
Ce va determina execuția comenzii următoare?

INSERT INTO tablou(id_tablou, titlu, an_creare)
VALUES (22, ‘Camp cu maci’, 1850);

28. Se dă tabelul:
SALARIATI (cod_ang#, nume, pren, data_angajarii, salariu,
cod_manager)
Ce va determina execuția comenzii următoare?

SELECT nume, salariu
FROM salariati
WHERE cod_manager =
(SELECT cod_ang
FROM salariati
WHERE UPPER(nume) ='POPESCU' AND UPPER(pren) ='ION' );

29. Pentru acționarii unei firme se consideră tabelul următor:
ACTIUNI (id_actionar#, nume, seriain, seriaout, valoare)
(unde seriain și seriaout reprezintă seria de început, respectiv de sfârș it a intervalului de acțiuni pe
care îl are un acționar).
Ce va determina execuția comenzii următoare?

SELECT SUM((seriaout -seriain+1)*valoare))
FROM actiuni;

30. Se dă următorul tabel:
MELODII(id_melodie#, titlu, textier, compozitor, gen, durata)
Ce va determina execuția comenzii următoare?

SELECT *
FROM melodii
WHERE textier IS NOT NULL;

104
31. Se dă următorul tabel:
ANGAJATI(id_angajat#, nume, prenume, data_angajarii, salariu,
comision, functia)
Ce va determina execuția comenzii următoare?

SELECT nume, functia, salariu
FROM angajati
WHERE salariu > ALL(SELECT salariu
FROM angajati
WHERE functia=’Programator IT’);

32. Se dă următorul tabel
MASINA(id_masina#, denumire, pret_unitar, stoc_curent, categorie,
localitate)
Ce va determina execuția comenzii următoare?

SELECT localitate, categorie, SUM(pret_unitar*stoc_curent)
FROM masina
GROUP BY ROLLUP (localitate, categorie);

33. Se dă următorul tabel:
ANGAJATI(id_angajat#, nume, prenume, data_angajarii, salariu,
functia, id_sef)
Ce va determina execuția comenzii următoare?

SELECT id_angajat, nume
FROM angajati
WHERE id_angajat IN (SELECT DISTINCT id_sef
FROM angajati);

34. Se dă următorul tabel:
SALARIATI(id_angajat#, nume, prenume, cnp, data_angajarii,
salariu)
Ce va determina execuția comenzii următoare?

ALTER TABLE SALARIATI
ADD (cod_functie NUMBER(2), email CHAR(25));

35. Se dă următorul tabel:
SALARIATI(id_angajat#, nume, prenume, cnp, data_angajarii,
salariu, cod_fuctie, email)
Ce va determina execuția comenzii următoare?

ALTER TABLE SALARIATI
DROP (cod_functie, email);

36. Se dau următoarele tabele:
TABLOU(id_tablou#, titlu, data_crearii, valoare, cod_pictor)
PICTOR(id_pictor#, nume, prenume, data_nasterii)
Ce va determina execuția comenzii următoare?

105
SELECT nume
FROM pictor, tablou
WHERE id_pictor = cod_pictor
GROUP BY nume
HAVING SUM(valoare) > = 200000;

37. Se dau următoarele două tabele:
FIRMA(id_firma#, denumire, oras)
ANGAJAT(id_angajat#, nume, prenume, salariu, cod_firma)
Ce va determina execuția comenzii următoare?

SELECT id_firma, denumire, AVG(salariu)
FROM angajat, firma
WHERE id_firma=cod_firma
AND oras=’Bucuresti’
GROUP BY id_firma, denumire;

38. Se dau următoarele două tabele:
CLIENTI(id_client#, nume, prenume, data_nasterii, id_agent)
AGENTI_IMOBILIARI(id_agent#, nume, prenume, salariu, comision)
Ce va determina execuția c omenzii următoare?

SELECT c.nume client, a.id_agent, a.nume agent
FROM clienti c LEFT OUTER JOIN agenti_imobiliari a
ON(c.id_agent = a.id_agent);

39. Se dau următoareale două tabele:
TABLOU (id_tablou#, denumire, data_crearii, valoare, cod_pictor)
PICTOR(id_pictor#, nume, prenume, data_ nastere)
Ce va determina execuția comenzii următoare?

SELECT cod_pictor, denumire, valoare
FROM tablou t1
WHERE valoare = (SELECT MAX(valoare)
FROM tablou t2
WHERE t1.cod_pictor = t2.cod_pictor);

40. Se dau următoareale două tabele:
TABLOU(id_tablou#, denumire, data_crearii, valoare, cod_pictor)
PICTOR(id_pictor#, nume, pren ume, data_nastere )
Ce va determina execuția comenzii următoare?

SELECT nume, prenume
FROM pictor
WHERE id_pictor IN (SELECT cod_pictor
FROM tablou
WHERE valoare >50000);

41. Se dau următoarele două tabele:

106
OPERA_ARTA (id_opera#, titlu, data_creare, nume_autor, pret,
cod_gen)
GEN(id_gen#, denumire, descriere)
Ce va determina execuția comenzii următoare?

UPDATE opera_arta
SET pret = pret * 0.5
WHERE cod_gen IN
(SELECT id_gen
FROM gen
WHERE denumire NOT IN ( ‘pictura’, ‘sculptura’));

42. Se dau următoarele tabele:
PRODUS(id_produs#, denumire, culoare, pret_unitar, stoc_curent,
cod_depozit)
DEPOZIT(id_depozit#, denumire, oras)
Observatie: Stocul curent nu poa te fi mai mic decâ t 1.
Ce va determina execuția comenzii următoare?

SELECT d.denumire
FROM produs p, depozit d
WHERE p.cod_depozit = d.id_depozit
GROUP BY d.denumire
HAVING COUNT(*) >= 1000;

43. Dacă în tabelul ANGAJAT sunt men ținute informații despre angajați, respectiv despre
departamentul și jobul pe care lucrează în prezent, iar în tabelul ISTORIC_ANGAJAT informații
despre departamentele și joburile pe care au lucrat aceștia în trecut, atunci ce va determina
execuția comenzi i următoare?

SELECT id_angajat, cod_departament, cod_job
FROM angajat
INTERSECT
SELECT id_angajat, cod_departament, cod_job
FROM istoric_angajat;

44. Se dau următoarele trei tabele:
FACTURA(id_factura#, data_emitere)
CONTINE(cod_factura#, cod_produs#, cantitate)
PRODUS(id_produs#, denumire, pret_unitar)
Observație: Facturile conțin produsele vândute.
Ce va determina execuția comenzii următoare?

SELECT id_factura, SUM(cantitate*pret_unitar)
FROM contine c , produs p , factura f
WHERE c.cod_produs = p.id_produs
AND c .cod_factura = f.id_factura
AND TO_CHAR(data_emitere,’YYYY’) =TO_CHAR(sysdate,’YYYY’)
GROUP BY id_factura;

45. Se dau urmă toarele trei tabele:

107
ANGAJAT(id_angajat#, nume, prenume, data_angajarii)
LUCREAZA (id_angajat#, id_proiect#, data_inceput, data_sfarsit)
PROIECT(id_proiect#, denumire, descriere, data_lansare,
data_predare)
Ce va determina execuția comenzii următoare?

SELECT p.id_proiect, denumire, nume
FROM angajat, lucreaza l, proiect p
WHERE l.id_angajat = angajat.id_angajat
AND lucreaza.id_proiect = p.id_proiect
AND data_sfarsit<sysdate;

46. Se dau urmă toarele trei tabele:
ANGAJAT(id_angajat#, nume, prenume, data_angajarii)
LUCREAZA(id_angajat#, id_proiect#)
PROIECT(id_proiect#, denumire, descriere, data_lansare,
data_predare)
Ce va determina execuția comenzii următoare?

SELECT id_angajat, nume
FROM angajat
WHERE id_angajat IN (SELECT l.id_angajat
FROM lucreaza l, proiect p
WHERE l.id_proiect = p.id_proiect AND
TO_CHAR(data_lansare,’YYYY’) = 2017 );

47. Se dă următorul tabel:
ANGAJAT(id_angajat#, nume, prenume, data_angajarii, job, sal,
ore_suplimentare, plata_ora)
Ce va determina execuția comenzii următoare?

UPDATE angajat
SET plata_ora = ROUND(plata_ora * 1.1, 2)
WHERE jo b =‘IT‘ ;

48. Se dau următoarele două tabele:
EMP(empno#, ename, job, mgr, hiredate, sal, comm, deptno)
DEPT(deptno#, dname, loc)
Ce va determina execuția comenzii următoare?

SELECT mgr, MIN(sal)
FROM emp
WHERE mgr IS NOT NULL
GROUP BY mgr
HAVING MIN(sal) > 3000
ORDER BY MIN(sal) DESC

49. Se dau următoarele două tabele:
EMP(empno#, ename, job, mgr, hiredate, sal, comm, deptno)
DEPT(deptno#, dname, loc)

108
Ce va determina execuția comenzii următoare?

SELECT YEAR(hiredate) [Anul angajarii],
COUNT(hiredate) [Nr. de angajati]
FROM emp
GROUP BY YEAR(hiredate)

50. Se dă următorul tabel:
DATE_PERSOANA(CNP, nume, prenume, nr_telefon, simbol_judet)
Ce va determina execuția comenzii următoare?

SELECT CONCAT('Numele: ',nume) AS numele,
CONCAT('Anul: ', '19',SUBSTR(CNP,2,2),' , ',
'Luna:',SUBSTR(CNP,4,2), ', ',
'Ziua: ',SUBSTR(CNP,6,2)) AS data_nasterii
FROM DATE_PERSOANA;

51. Se dă următorul tabel:
SALARIATI(id_angajat#, nume, prenume, cnp, email, data_angaj arii,
salariu, cod_fuctie )
Scrieți comanda care șterge din tabelul SALARIATI coloanele cod_fuctie și email .

52. Se dă următorul tabel:
ANGAJATI(id_angajat#, nume, data_angajarii, salariu, functia,
id_sef)
Scrieți comanda care obține numărul de angajați care au șef.

53. Se dă următorul tabel:
SALARIATI(id_angajat#, nume, prenume, cnp, data_angajarii,
salariu, cod_fuctie)
Scrieți comanda care afișează numele salariaților care câștigă mai mult decât salariul mediu pe
companie, în ordine crescătoare a salariului.

54. Pentru tabelele:
PROFESORI(id_prof#, nume, pren, salariu)
COPII(id_copil#, nume_copil, varsta, id_prof)
Scrieți secvența pentru a afișa profesorii fără copii?

55. Se dau următoarel e două tabele:
ANGAJAT (id_angajat#, nume, prenume, data_angajarii, id_job,
id_departament)
ISTORIC_JOB (id_angajat#, data_inceput#, data_sfarsit, id_job,
id_departament)
Observație: Tabelul ANGAJAT conține informații prezente referitoare la departamentul și jobul
angajatului, iar tabelul ISTORIC_JOB conține informații referitoare la trecut.
Scrieți comanda care obține codul, numele și prenumele tuturor angajatilor firmei care au lucrat la un
moment dat în departamentul 3 0 și în prezent lucrea ză în alt departament.

56. Se dau următoarele două tabele:
TABLOU(id_tablou#, titlu, an_creatie, nume_pictor, id_sala)
SALA(id_sala#, denumire, capacitate)

109
Scrieți comanda care afișează toate sălile în care nu sunt expuse tablouri.

57. Se dă următorul tabel:
STUDENTI ( nume, prenume, nrmatricol, grup a, datan, adresa, media)
Pentru fiecare grupă se cere numărul de studenti, media minimă și media maximă.

58. Se dau următoarele două tabele:
STUDENTI (nume, prenume, nrmatricol, grupa, datan, adresa, media)
PROFILE (denumire, cod_grupa)
Se cer studenții de la grupele cu profil electric(ELE) sau mecanic(MEC), care au media mai mare
decât media generală a studenților de la aceste grupe.

59. Se dă următorul tabel :
PROFESOR( Cod_Prof #, Nume, Disciplina, DataN, Telefon)
Se cere numărul de profesori născuți în același an.

60. Se dă următorul tabel:
PROFESOR(Cod_Prof#, Nume, Disciplina, DataN, Telefon)
Se cer profesorii care sunt născuți în număr de 2 în același an.

61. Se dau următoarele două tabele:
SCOALA(Cod_Scoala#, Nume, Adresa, Cod_Director, Cod_Prof)
PROFESOR(Cod_Prof#, Nume, Disciplina, DataN, Telefon)
Se cer toate datele despre profesorii de la școala cu codul ’S308’, cu vârsta mai mică decât vârsta
medie a profesorilor (din toate școlile).

62. Se dau următoarele d ouă tabele:
SCOALA(Cod_Scoala#, Nume, Adresa, Cod_Director, Cod_Prof)
PROFESOR(Cod_Prof#, Nume, Disciplina, DataN, Telefon)
Se cer toate datele despre profesorii care au cel putin un director mai tânăr decât ei .

63. Se dau următoarele două tabele:
PROFESORI(cod_profesor#, nume, prenume, salariu, cod_facultate)
TOTALURI (cod_facultate#, nr_profesor, total_salarii)
Folosind informațiile din tabelul PROFESORI, se cere să se insereze în tabelul TOTALURI numărul
de profesori și suma totală a salariilo r pentru fiecare facultate.

64. Se dă următorul tabel:
DATE_PERSOANA(CNP, nume, prenume, nr_telefon, simbol_judet)
Modificați toate numerele de telefon din județul Maramureș, astfel ca prefixul să nu mai fi e 0262 ci
0362 .

65. Se dau următoarele trei tabele:
FURNIZORI (id_furnizor#, numeF, oras)
COMPONENTE(cod_componenta#, numeC, culoare, localitate )
FURNIZORI_COMPONENTE(id_furnizor#, cod_componenta#, cantitate)
Se presupune că fiecare furnizor este localizat în exact un singur oraș.
Se cere numele furnizorilor ce au livrat componenta C2 și care sunt orașele d in care provin acești
furnizori.

110
66. Se dau următoarele trei tabele:
FURNIZORI (id_furnizor#, numeF, oras)
COMPONENTE(cod_componenta#, numeC, culoare, localitate)
FURNIZORI_COMPONENTE(id_furnizor#, cod_componenta#, cantitate)
Se presupune că fiecare furnizor este localizat în exact un singur oraș.
Se cere să se determine componenta roșie care s-a livrat în cea mai mare cant itate.

67. Se dau următoarele trei tabele:
FURNIZORI (id_furnizor#, numeF, oras)
COMPONENTE(cod_componenta#, numeC, culoare, localitate)
FURNIZORI_COMPONENTE(id_furnizor#, cod_componenta#, cantitate)
Se presupune că fiecare furnizor este localizat în exact un singur oraș.
Se cere să se determine d in ce oraș provine furnizorul cu cele mai puține componente vândute, și care
sunt aceste componente.

68. Se dau următoarele două tabele:
EMP(empno#, ename, job, mgr, hiredate, sal, comm, deptno)
DEPT(deptno#, dname, loc)
Să se afișeze angajații cu același job ca și angajatul cu codul 7369 și cu salariul mai mare decât al
angajatului cu codul 7876.

69. Se dau următoarele două tabele:
EMP(empno#, ename, job, mgr, hiredate, sal, comm, deptno)
DEPT(deptno#, dname, loc)
Să se afișeze toate departamentele cu salariul minim mai mare decât salariul minim di n departamentul
20.

70. Se dau următoarele două tabele:
EMP(empno#, ename, job, mgr, hiredate, sal, comm, deptno)
DEPT(deptno#, dname, loc)
Să se afișeze codul, numele și jobul angajaților cu salariul mai mic decât cel al oricărui funcționar și
care nu sunt funcționari.

71. Se dau următoarele două tabele:
EMP(empno#, ename, job, mgr, hiredate, sal, comm, deptno)
DEPT(deptno#, dname, loc)
Să se afișeze angajații (nume, nr. departament, salariu și comision) ale căror salariu și comision sunt
identice cu cele ale oricărui angajat din departamentul 30.

72. Se dau următoarele două tabele:
EMP(empno#, ename, job, mgr, hiredate, sal, comm, deptno)
DEPT(deptno#, dname, loc)
Să se afișeze numele angajatului cu cea mai mare vechime în muncă și cel mai nou în firmă.

73. Se dau următoarele două tabele:
EMP(empno#, ename, job, mgr, hiredate, sal, comm, deptno)
DEPT(deptno#, dname, loc)
Să se afișeze maximul, minimul și suma salariilor pentru fiecare tip de job, în cadrul fiecărui
departament.

111
74. Se dau următoarele două tabele:
EMP(empno#, ename, job, mgr, hiredate, sal, comm, deptno)
DEPT(deptno#, dname, loc)
Să se afișeze angajații care au subordonați (sunt șefi).

75. Se dau următoarele două tabele:
EMP(empno#, ename, job, mgr, hiredate, sal, comm, deptno)
DEPT(deptno#, dname, loc)
Să se afișeze salariul total plătit pe fiecare job în cadrul fiecărui departament.

Bibliografie

1. Bâscă, O., Baze de date, Editura All, 1997;
2. Cârstoiu, D., Baze de date, Editura Matrix ROM, 2009;
3. Connolly T., Begg C., Database Systems. A Practical Appro ach to Design, Implementation,
and Management, Ed. Addison Wesley, 2005
(http://www.palinfonet.com/download/software2/database%20systems.pdf);
4. Fusaru D., Arhitectura bazelor de date -mediul SQL, Editura Fundației România de Mâ ine,
2002;
5. Popa M., Baze de da te (Fundamente, exemple, test e de verificare), Editura Fundaț iei România
de Mâine, București 2006;
6. Popescu I., Modelarea bazelor de date, Editura Tehnică, 2002;
7. Popescu M, Baze de date, Editura Renaissance, București, 2010 ;
8. ***, Informatică pentru examenul de licență, Universitatea SPIRU HARET, 2017;
9. ORACLE, https://www.oracle.com/index.html.

112

113

III. Structuri de date
Dana -Mihaela Vîlcu , Lector univ. dr., Universitatea Spiru Haret

Înțelegem prin structură de date o modalitate de organizare a datelor folosite în programare. Structurile
de date avute în vedere în lucrarea de față sunt structurile elementare: tablouri, liste, stive, cozi și
arbori, cu precădere arborii bi nari. Modul de organizare al fiecărei structuri de date este definitoriu
acesteia. De aceea, orice operație care se efectuează asupra unei structuri de date nu trebuie să -i
altereze proprietățile induse de organizarea ei. Pentru fiecare din structurile men ționate sunt indicate,
în primul capitol: definiția, exemple, proprietăți, operații de bază. O secțiune aparte prezintă operații
mai complexe, respectiv sortarea și căutarea datelor, cu algoritmii lor specifici. Capitolul al doilea
aduce probleme de consol idare a înțelegerii conceptelor prezentate. Unele dintre acestea sunt întrebări
cu răspuns direct din capitolul teoretic, altele necesită rezolvăr care se obțin prin aplicare imediată a
noțiunilor teoretice. Cel de al treilea capitol este de aprofundare și exersare, conținând probleme
pentru a căror rezolvare este nevoie de aplicarea și utilizarea noțiunilor teoretice într -un mod mai
elaborat, mai amplu.

Pentru elaborarea acestui material am avut în vedere faptul că aceste noțiuni trebuie să facă parte din
cultura generală a oricărui informatician. Am dorit ca materialul să fie, totodată, un îndrumar și o cheie
de verificare a cunoștințelor pentru structurile de date menționate, util în pregătirea pentru a face față
unei tirade de întrebări de interviu la c are se așteaptă răspuns imediat, util în pregătirea examenului de
finalizare a studiilor de liecnță..

Menționăm că am preluat informațiile așa cum sunt ele prezentate în literatura de specialitate indicată
în bibliografie, dar am intervenit la nivel de fo rmă pentru a le da un caracter unitar și succint,
corespunzător cerințelor acestei lucrări. Lucrarea [1] a reprezentat suportul de curs al promoției 2017
pentru studenții la Informatică din cadrul Universității Spiru Haret, la disciplina Structuri de date . De
aceea această lucrare este referința principală pentru partea teoretică a materialului de față, cuprinsă în
capitolul întâi. În ceea ce privește problemele incluse în capitolele 2 și 3, unele fac parte deja din
„folclor”, ele regăsindu -se, într -o formă sau alta, pe numeroase site -uri, lucrări, manuale, cărți. Ele nu
îmi aparțin însă nici nu li se poate identifica autorul. Există și probleme mai aparte, îndeosebi în
ultimul capitol, pentru care referințele sunt clare și sunt specificate ca atare.

O dese nare facilă a arborilor ne -a fost înlesnită de utilizarea editorului yEd Graph Editor
(http://www.yWorks.com).

1. Noțiuni teoretice – prezentare
1.1 Structuri de date statice: vectori, matrici

114
Matricele intervin în numeroase aplicații, nu doar matematice, de calcul științific. Astfel, valorile
numerice stocate pot avea semnificații aparte, matricea nefiind decât o modalitate de aranjare a acestor
date în contextul problemei (vezi, de exemplu, re prezentrea grafurilor și numeroasele probleme care se
reduc la probleme pe grafuri). Mai mult, se folosește adesea o extindere a ideii de matrice, ca
modalitate de structurare a unor date de același tip, nu neapărat numeric. Asemenea structurări de date
le găsim în programare, în mod uzual, sub denumirea de tablou (en. array ).

Implementarea matricilor poate fi făcută static sau dinamic. Punem în evidență, în cele de mai jos,
elementele fundamentale ale implementării statice , pentru diverse tipuri de matric i.

Înțelegem prin vector o mulțime finită și ordonată în care:
 numelui vectorului, v, i se asociază o adresă unică de memorie ce reprezintă locația în
care se memorează primul element al vectorului (numele vectorului memorează adresa
fizică a primului el ement al vectorului);
 elementele vectorului se stochează în spațiul programului, într -o zonă compactă de locații
libere, succesive;
 dimensiunea spațiului de memorare a vectorului este dată de produsul dintre numărul
elementelor vectorului și dimensiunea un ui element, conform tipului său.

Numerotarea elementelor unui vector începe de la 0. Indicele, sau poziția relativă a fiecărui element în
cadrul vectorului se mai numește și rang al elementului în cadrul vectorului. Astfel, al m-lea element al
vectorului are rangul m-1 (este elementul v[m-1]).

Implementarea tablourilor bidimensionale (a matricilor) se bazează pe implementarea statică a
vectorilor și se face prin linearizare linie (C++, Java) sau coloană (FORTRAN) a elementelor matricii
date. Astfel, numel e matricii conține adresa primului element al acesteia. Celelalte elemente se
stochează în locații succesive de memorie din spațiul programului, unele după altele, în ordinea liniilor
(întâi elementele primei linii, apoi cele de pe linia a doua, ș.a.m.d.) sau a coloanelor, după felul
linearizării.

Se consideră rang al elementului unui tablou, în cadrul vectorului obținut la implementarea statică,
prin linearizarea tabloului, ca fiind poziția relativă a elementului în cadrul tabloului. Astfel, într -o
linear izare linie, a unei matrici a de dimensiune m x n , rangul elementului ai,j este (i-1)*n+j-
1.

În cazul implementării matricilor particulare, pentru optimizarea spațiului ocupat, se recurge la
stocarea numai a părții potențial nenule, de asemenea prin linearizare linie sau coloană.

O matrice pătrată, nxn, este superior (inferior) triunghiulară dacă toate elementele de sub (deasupra)
diagonalei principale sunt nule. Stocând numai elementele diagonalei principale și a celor de deasupra
ei, dimensiunea ve ctorului de linearizare devine n*(n+1)/2 . Putem vorbi despre rang al unui
elementului, ai,j, numai dacă acesta apare în vectorul de linearizare. Astfel, pentru o matrice superior
(inferior) triunghiulară se au în vedere numai elementele pentru care i ≤ j (respectiv i ≥ j ), iar
valoarea rangului se determină ca [n+(n-1)+…+(n-i+1)]+(j -i+1)-1. În mod asemănător se
fac calculele și pentru situațiile de linearizare coloană, respectiv pentru matrice inferior triunghiulară,
separat pentru fiecare din cele două tip uri de linearizări.

O situație similară matricilor triughiulare avem la implementarea matricilor simetrice (pentru care ai,j
= aj,i, pentru orice i ≠ j ).

115
În cazul matricilor diagonale (elemente nenule pot fi numai pe diagonala principală, respectiv pe cele
k diagonale imediat alăturate acesteia, pentru matricea k-diagonală), implementarea se poate face și
prin stocarea elementelor de pe diagonale în ordinea diagonalelor, dar și obișnuit, prin linearizare linie
sau coloană, cu păstrarea numai a elementelor diagonalelor.

1.2 Liste

Înțelegem prin listă (listă liniară) o organizare conceptuală a unor date de același tip sub forma unui
șir, eventual vid . O listă n evidă conține un prim și un ultim element , nu neaparat distincte. În cadrul
oricărei aplicații, prima referire la o listă trebuie să fie inițializarea ei (cel mai adesea ca listă vidă).

Dintre operațiile care se pot efectua asupra listelor menționăm și av em în vedere în cele ce urmează:
 accederea la un element pe baza poziției sau pe baza valorii sale,
 inserarea unui element într -o poziție dată prin numărul de ordine sau relativ la valoarea
unui alt element din listă,
 eliminarea unui element dat prin poziție sau valoare,
 concatenarea a două liste,
 descompunerea unei liste în două sau mai multe liste,
 realizarea copiei unei liste,
 ordonarea elementelor unei liste,
 interclasarea a două sau mai multe liste.

Dacă lista este de un tip particular, efectuare a operațiile menționate trebuie să conserve
particularitatea listei inițiale și să transfere proprietățile și eventualelor liste rezultate.

Implementarea listelor se poate face secvențial sau înlănțuit.

1.2.1 Liste implementate secvențial

Implementarea secvențială presupune stocarea elementelor listei în ordine, în locații de memorie
succesive, cu memorarea adresei primului element. Realizarea practică se face cu ajutorul unui vector.
Având în vedere, pe de o parte, caracterul fix al spațiului alocat ini țial, conform dimensionalității
vectorului, dar, pe de altă parte, faptul că lungimea listei este variabilă, este necesară și memorarea
poziției ultimului element din listă.

Implementarea operațiilor menționate anterior trebuie să aibă în vedere stocarea listei ca vector, cu
memorarea poziției primului element al listei, și continuitatea locațiilor de memorie. Astfel, adăugarea
sau scoaterea unui element din listă induce și deplasări ale celorlalte elemente din listă ceea ce face
aceste operații costisitoa re. De remarcat însă accederea rapidă la un element dat prin poziția sa. Pe de
altă parte, din caracterul static al vectorilor, spațiul utilizat pentru stiva programului fiind constant și
declarat de la început, poate fi mult mai mare decât spațiul efectiv utilizat pentru stocarea listei
propriu -zise.

1.2.2 Liste înlănțuite

Implementarea înlanțuită a listelor liniare presupune stocarea fiecărui element al listei într -o celulă
formată din două părți, conținând:
 datele corespunzătoare elementului de stocat și, respectiv,
 informație de legătura către elemental următor din listă (de regulă adresa acestuia).

116

Astfel, considerăm pentru configurarea structurii unui nod:
node->data = valoarea datelor stocate în elementul nod,
node->next = adresa elementului următor.

Se memorează adresa primului element în V, iar partea de legătură a ultimului element primește o
valoare ce nu poate desemna nicio legătură (NULL).

Implementarea înlănțuită elimină necesitatea efectuării de deplasări ale el ementelor listei la adăugarea
sau scoaterea unui element din listă. De asemenea, în implementarea înlănțuită dinamică, prin
eliberarea spațiului ocupat de celule care se șterg se obține o mai bună gestionare a memoriei.

În descrierea operațiilor de prelucrare a listelor avem în vedere noțiunile de:
 overflow (depășire superioară) – întregul spațiu alocat structurii considerate este
ocupat și se încearcă adăugarea încă a unui element, ceea ce face ca operația să eșueze.
 underflow (depășire inferioară) – structura considerată este vidă și se încearcă
eliminarea unui element, ceea ce face ca operația să eșueze.

De asemenea avem în vedere apelarea unor rutine de alocare și eliberare a memoriei, numite în cele de
mai jos:
 alloc() – pentru alocarea unei ce lule de memorie și returnarea adresei ei, și
 free(k) – pentru eliberarea celulei a cărei adresă se transmite ca parametru.

În cadrul algoritmilor de mai jos considerăm făcute apriori declararea tuturor variabilelor
suplimentare, precum și alocarea spațiului necesar lor cu verificarea disponibilității acestuia sub
forma:
node = alloc();
if (node == NULL)
then overflow // tratare caz heap plin
else { // operația de efectuat

}

Pentru o bună gestionare a memoriei și evitarea pointării către obiecte care nu sunt valide, eliberarea
unei celule va fi urmată imediat de inițializarea zonei cu NULL :
free(k); k = NULL;

Dezavantajul alocării dinamice este dat de necesitatea parcurgerii listei element cu element pentru
fiecare operație. Pentru o parcurgere mai facilă se pot aborda și alte metode de implementare a
informației de legătură, ce duc astfel la liste particulare cum ar fi:
 lista dublu înlănțuită – păstrează două câmpuri de legătură, pentru memorarea adresei
elementului următor și a celui predecesor;
 lista circulară – valoarea câmpului de legătură al ultimului element este adresa primului
element din listă.

Implementarea dinamică a unei liste sim plu înlănțuite

117
Inițializarea listei
V = NULL;

Adăugarea elementului cu valoarea x ca prim element
node->data = x; node ->next = V; V = node;

Adăugarea elementului cu valoarea x ca ultim element

node->data = x; node ->next = NULL; // nodul de adaugat
if (V == NULL) // lista e vida
then V = node;
else
{ // parcurgerea listei pana la ultimul nod
p = V;
while (p->next != NULL)p = p->next;
// inserarea noului nod
p->next = node;
}

Inserarea elementului cu valoarea x după al n-lea element din listă
node->data = x; node ->next = NULL; // nodul de inserat
p = V, i = 1
while (p != NULL and i < n){
p = p->next; i = i + 1;
}
if( p == NULL)
then overlist /* lista nu are n noduri; tratament particular */
else {
node->next = p ->next; p ->next = node;
}

Extragerea primului element din listă, cu salvarea în z a datelor corespunzătoare lui
if (V == NULL)
then underflow // nu se poate scoate niciun element, lista e vida
else
{
node = V; z = node ->data; // extragerea primului nod
V = node ->next; // refacerea listei
free(node); // eliberarea celulei corespunzatoare lui node
node = NULL; // pentru control asupra continutului lui node
}

Extragerea ultimului element din listă, cu salvarea în z a datelor corespunzătoare lui
if (V == NULL)
then underflo w
else
{ // se parcurge lista cu doi pointeri consecutivi p și u
// pana cand p, primul, ajunge pe ultimul element

118
u = NULL; p = V;
while (p ->next != NULL)
{
u = p; p = p ->next;
}
if (u == NULL) // lista are un singur element
then V = NULL;
else u->next = NULL;
z = p->data; free(p); p = NULL;
}

Extragerea celui de al n -lea element din listă, cu salvarea în z a datelor corespunzătoare lui
if (V == NULL)
then underflow
else
{ // p parcurge lista pana la al t -lea element
// u adresa predecesorului lui p
if (V == NULL)
then underflow ;
else
u = NULL; p = V; t = 1;
while (p != NULL and t < n)
{
u = p; p = p ->next; t = t + 1 ;
if (p == NULL)
then overlist
else
if (u == NULL)
then V = NULL;
else u->next = p ->next;
}
z = p->data; free(p); p = NULL;

1.2.3 Liste dublu înlănțuite

Introducerea dublei înlănțuiri are ca scop o mai ușoară parcurgere a listei. Considerăm structura
nodurilor listei dublu înlănțuite dată de :
node->data = valoarea datelor stocate în elementul nod,
node->rlink = adresa elementului următor la dreapta,
node->llink = adresa elementului următor la stânga.

Pentru completarea construcției se memorarează și cele două capete ale listei firstL (pentru primul
element din stânga al listei), respectiv firstR (pentru primul element din dreapta). Fiind în același
timp și ultime elemente în sens invers, este valabil întotdeauna că firstL->llink și firstR-
>rlink să fie NULL .

Să observăm că operațiile se desfășoară asemănător celor de pe lista simplu înlănțuită. P articularitatea
dublei înlănțuiri implică percurgerea de fiecare dată cu ajutorul a doi pointeri, astfel încât, la operarea

119
propriuzisă în listă să se poată face legăturile atât cu nodul predecesor cât și cu nodul succesor celui
operat. Pentru exemplificar ea modului de lucru cu liste dinamice dubluîlănțuite prezentăm:

Inserarea elementului cu valoare x dupa nodul cu adresa t, în sensul de la stânga la dreapta
// inserarea nodului
node->data = x; node ->llink = t; node ->rlink = t ->rlink;
// completarea dublei legaturi din stanga nodului introdus
t->rlink = node;
// completarea dublei legaturi din dreapta nodului introdus
p = node ->rlink ;
if (p != NULL)
then p->llink = node;
else
// nodul introdus e ultimul din lista
firstR = node;

Eliminarea nodului de la adresa t
q = t->llink;
if (q == NULL)
then firstl = t ->rlink;
else q->rlink = t ->rlink;
q = t->rlink;
if (q == NULL)
then firstR = t ->llink;
else q->llink = t ->llink;
free(t); t = NULL;

1.3 Stive

Stiva este un caz particular de listă liniară, în care accesul se realizează întotdeauna doar la unul din
capete, numit vârful stivei . Elementul de la capătul opus se numește baza stivei . Astfel, principiul de
funcționare al stivei este “ultimul venit – primul servit”, sau LIFO (en. Last In – First Out). Prin
urmare, extragerea unui anumit element oarecare din stivă se face numai numai după ce au fost extrase
elementele care au intrat în stivă după și peste acesta.

1.3.1 Stiva secvențială

Implementarea secvențială a unei stive S presupune stocarea elementelor sale într -un vector a cu m
elemente și prelucrarea după cum urmează: dacă v = vârful stivei, el se incremenentează odată cu
adăugarea unui element, respectiv se decrementează la extrag erea unui element din stivă. Operațiile
elementare sunt descrise după cum urmează:

Inițializarea stivei
v = 0;

Adăugarea elementului x la stivă (x=>S)
if (v == m) //stiva este plina
then overflow ;

120
else { v = v+1; a[v] = x; }

Extragere unui element din stivă (z<=S)
if (v == 0) // stiva este vida
then underflow ;
else { z = a[v]; v = v –1; }

Stive secvențiale cu spațiu partajat
Prezentăm (adaptat din [1]) modalitatea de implementare a lucrului cu mai multe stive, S 1,…,S n, cu
informații de același tip, ce partajează același spațiu, un vector a cu m elemente. Stivele se stochează
una în continuarea celeilalte. a[1] reprezintă baza primei stive nevide. Se folosește un vector
suplimentar v, cu n+2 elemente, pentru memorarea vârfurilor stivelor: v[i] este vârf ul stivei S i. Se
consideră stivele fictive S 0 și Sn+1 cu vârfurile în v[0], respectiv în v[n+1] (elemente fictive).

Inițializare
for(i=0; i<=n+1; i++) v[i]=0;

Adăugarea elementului x la stiva S i (x => S i)
if(v[n+1] == m) // intreg spatiul este plin
then overflow
else
{
//decalarea stivelor următoare
for(j=v[n+1]; j >=v[i]+1; j–) a[j+1] = a[j];
//adaugarea propriuzisa a elementului x
a[v[i]] = x;
//actualizarea deplasarii varfurilor precedente
for(j=i; j<=n+1; j++) v[j] = v[j]+1;
}

Extragerea elementului z din stiva S i (z<=S i)
if(v[i-1] == v[i]) //stiva i este vida
then underflow ;
else
{
//extragere elementului
z=a[v[i]];
//translatarea la stanga a stivelor urmatoare
for(j=v[i]+1; j<=v[n+1]; j++) a[j –1]=a[j];
//actualizarea varfurilor stive lor
for(j=i; j<=n+1; j++) v[j]=v[j] –1;
}

1.3.2 Stiva simplu înlănțuită

Păstrând notațiile utilizate la implementarea listelor și având în vedere sensul legăturilor de la vârful
stivei spre bază, precum și a proprietăților specifice stivei, putem descrie:

121
Inițializarea stivei
V = NULL;

Adăugarea elementului x la stivă (x=>S)
a = alloc();
if(a==NULL) //nu se mai poate aloca spațiu in heap
then overflow ;
else
{
//introducerea elementului
a->data=x; a ->next=V;
//refacerea stivei
V=a;
}

Extragerea unui element din stivă (z<=S)
if (V == NULL) // stiva este vida
then underflow ;
else
{
//recuperarea informației
a=V; z=a ->data;
//refacerea stivei
V=V->next;
//disponibilizarea celulei
free(a); a=NULL;
}

1.4 Cozi

Coada este o listă liniară în care introducerile de noi elemente se fac pe la un singur capăt ( baza
cozii ), iar extragerile se fac pe la capătul opus ( vârful cozii ). Astfel, principiul de funcționare este
“primul venit – primul servit”, sau FIFO (en. First In – First Out). De aceea scoaterea unui anumit
element din coadă, se face numai numai după ce au fost extrase elementele care au intrat în coadă
înaintea/în fața acestuia.

Coada completă este lista liniară în care depunerile și extragerile sunt permise la o ricare din capetele
listei ( fie el capăt stâng , sau capăt drept ).

Dacă la unul dintre capetele cozii complete nu sunt permise operații de depunere (extragere) atunci
spunem că avem o coadă completă restricționată la intrare (ieșire ) la acel capat.

Coada cu priorități este un caz particular de coadă în care fiecare element are asociată o valoare și o
prioritate. Cu cât valoarea priorității unui element este mai mică, cu atât elementul este mai prioritar.
Astfel, în coada cu priorități, elementele sun t ordonate crescător în raport cu prioritățile lor, de la vârf
către bază, astfel încât nodul cel mai prioritar (cea mai mică prioritate) iese primul.

122
Ținând cont de aspectele menționate, introducerea unui element de prioritate q se face între două
nodur i consecutive cu priorități mai mică și, respectiv, mai mare, sau, în cazul mai multor noduri cu
priorități egale și egale cu cea a nodului introdus, acesta va fi primul dintre ele, dinspre bază, pe unde
se și introduce.

1.4.1 Coada secvențială

Implementarea secvențială a unei cozi se face cu ajutorul unui vector a de m elemente. Întotdeauna
unul din capete este a[0]. Celălalt capăt se memorează ca poziție P în vector. În cele de mai jos
prezentăm prelucrările elementare în care a[0] este baza co zii, iar P este vârful.

Inițializare
P=0;

Implementare secvențială cu decalare unitară a unei cozi

Adăugarea elementului x la coadă
if(P == m) //nu mai e loc la coada
then overflow ;
else
{
// se face loc în bază noului element
for(i = P; i >=0; i–) a[i+1]=a[i];
// se actualizează indicatorul vârfului
P = P+1;
// se introduce in baza noul element
A[0] = x;
}

Extragere unui element
if(P == 0) // coada vida
then underflow ;
else { z = a[P]; P = P –1; }

Implementarea sevențială cu decalare multiplă a unei cozi
Decalarea se face doar în cazul în care depunerea ar depăși spațiul alocat vectorului în care se
stochează coada. În felul acesta se optimizează timpul general de efectuare a adăugării unui element,
prin eliminarea deplasărilor, și efectuarea tu turor acestora la un moment dat.

Pentru exemplificare considerăm că extragerea se face spre a[0], că B indică baza cozii și V elementul
de dinaintea celui din vârful cozii .

Adăugarea elementului x
if(P == m)
then

123
if (V == 0)
then overflow ;
else
{
for (i=V+1;i<=B;i++)
a[i-V]=a[i];
B=B-V; V=0;
}
B=B+1; a[B]=x;

Implementare sevențială circular ă a unei cozi
Implementarea circulară are rolul de a evita efectuarea decalărilor. Ca în cazul precedent, B reprezintă
baza cozii, V elementul de dinai ntea celui din varful cozii . Astfel:

Adăugarea elementului x
if(V == mod(m, B))
then overflow ;
else
{
B = mod(m, B+1);
a[B] = x;
}

Extragere unui element
if(V == B)
then underflow;
else
{
V = mod(m, V+1);
z = a[V];
}

Implementarea cozii secvențiale complete se poate face cu decalare unitară, multiplă sau circulară.
Tehnica pentru fiecare dintre acestea a fost prezentată și se aplică precum în cele de mai sus, ținând
cont că operațiile de adăugare și de scoatere a unui element se pot face pe la oricare dintre cele două
capete ale cozii. Pentru mai multe detalii se poate consulta [1].

Coada secvențială cu priorități
O variantă a implementării secvențiale a cozii cu priorități presupune folosirea a doi vectori, unul, v,
pentru stocarea valorilor propriu -zise ale elementelor, și un altul, pr, pentru stocarea priorităților
corespunzătoare valorilor stocate în v. Astfel, pentru introducerea unui element în coadă trebuie
parcurs vectorul priorităților pentru identificarea poziției pe care trebuie să intre noul element. Cei doi
vectori, de valori și de priorități se actualizează sincron. Extragerea nu pune probleme, se extra ge
primul element din ambii vectori.

Adăugarea elementului x cu prioritate y
if(P == m)

124
then overflow ;
else
{
pr[P+1]= -1; k=1;
while(p[k] > y)
k++;
for (i=P+1; i>= k; i++)
{
v[i+1]=v[i];
pr[i+1]=pr[i];
}
P++;
v[k]=x; pr[k]=y;
}

Atunci când limbajul de programare permite, lucrul cu doi vectori poate fi evitat dacă se recurge la o
implementare a cozii printr -un singur vector ale cărui elemente au structură de articol.

1.4.2 Coada simplu înlănțuită

Folosim aceleași notații ca în descrierea implementării dinamice a listelor simplu înlănțuite, stocând în
fiecare nod informație utilă în data și informație de legătură către nodul următor în next. De asemenea,
considerăm făcute apriori declararea tuturor variabilelor suplimentare folosite și alocarea spațiului
necesar lor cu verificarea disponibilității acestuia.

Cele două capete ale cozii sunt B, baza, și V, vârful. Depunerile se fac la bază și extragerile din vârf, și
se desfășoară după cum urmează:

Sensul legăturilor este de la V la B

Inițializarea listei
V = B = NULL;

Adăugarea unui element
a = alloc();
if (a == NULL)
then overflow
else
{
a->data = x; a ->next = NULL;
if (V == NULL)
then V=a;
else B->next=a
B=a;
}

Extragerea unui element din coad ă
if (V == NULL)

125
then underflow
else
{
a = V; V = a ->next;
if (V == NULL)
then B = NULL;
z = a->data;
free(a);
}

Sensul legăturilor este de la B la V
Adăugarea unui element
a = alloc();
if (a == NULL)
then overflow
else
{
a->data = x; a ->next = B;
B = a;
}

Extragerea unui element din coad ă
if (B == NULL)
then underflow
else
{
u = NULL; i = B;
while (i ->next != NULL)
{
u = i; i = i ->next;
}
if (u == NULL) //s-a scos ultimul element
then B = NULL;
else u->next = NULL;
z = i->data; free(i);
}

Coad ă circulară simplu înlănțuită
Adăugarea unui element
a = alloc();
if (a == NULL)
then overflow
else
{
a->data = x;
if (V == NULL) //coada era vida
then
{

126
V = a; a ->next = a;
};
else
{
a->next=V->next; V ->next=a;
}
}

Extragerea unui element din coad ă
if (V == NULL)
then underflow
else
{
z = V->data;
if (V->next == V) // coada avea un singur element
then
{
free(V); V = NULL;
}
else
{
a = V;
while (a ->next != V)
a = a->next;
b = V;
a->next = b ->next; V = a;
free(b);
}
}

1.4.3 Coada dublu înlănțuită

În cazul cozilor cu dublă înlănțuire, tehnicile de adăugare, respectiv de extragere a unui element
trebuie să aibă în vedere refacerea legăturilor în ambele sensuri. Tehnica a fost prezentată anterior pe
cazul li stelor dublu înlănțuite. (Pentru detalii de implementare se poate consulta, de exemplu, [1].)

1.5 Structuri arborescente – noțiuni fundamentale

Se numește arbore structura de date alcătuită din noduri și conexiuni unidirecționale ( arce) între
acestea așa încât să nu conțină cicluri.
Arborele fără noduri este un arbore vid .
Un arbore nevid are un unic nod, numit rădăcină , în care nu intră niciun arc.
Nodurile în care intră arcele care pleacă dintr -un nod se numesc noduri fii ai nodulu i dat, iar acesta se
numește nod părinte .
Un nod fără fii se numește nod frunză (sau nod extern ).
Orice nod părinte se mai numește nod intern , sau nod de ramificare .
Gradul unui nod este dat de numărul copiilor acelui nod.
Orice nod la care se poate aj unge pe sensul arcelor de la un nod dat se numește descendent al acestuia.

127
Drumul dintre un nod și un descendent al său este succesiunea de noduri și arce care le leagă prin
descendență.
Nivelul unui nod = numărul de conexiuni dintre nod și rădăcină. Rădă cina are nivel 0.
Înălțimea unui nod este dată de numărul de arce al celui mai lung drum de la nod la o frunză.
Înălțimea unui arbore = înălțimea rădăcinii arborelui.
Adâncimea unui nod = numărul de arce de la rădăcină la nod.
Pădurea este mulțimea formată din cel puțin doi arbori disjuncți.

Figura 1-1

Cel mai adesea arborii se reprezintă planar, nodurilor fii impunându -li-se o relație de ordine. De regulă
nodurile de pe un același nivel se plasează pe o aceeași linie orizontală.

Dacă ordinea relativă a fiilor fiecărui nod este semnificativă pentru definirea arborelui, spunem că
avem un arbore ordonat .

În Figura 1-1 este reprezentat un arbore oarecare. Rădăcina sa stochează valoarea 1. Nodurile 5, 9, 10,
8 și 4 sunt noduri frunze. Nodul 3 are ca părinte pe 1 și ca fii pe 7 și 8. Nodul 10 este și el desce ndent
al lui 3. Drumul de la 3 la 10 este: 3, 7, 10. Rădăcina se află pe nivelul 0. Nodurile de pe nivelul 3
sunt: 9 și 10. Înălțimea nodului 3 este 2. Adâncimea nodului 3 este 1. Înălțimea arborelui, dată de
lungimea celui mai lung drum de la tădăcină la o frunză, este 3 (ex. drumul de la 1 -9, sau de la 1 -10).

Se numește parcurgere a arborelui o examinare sistematică a nodurilor astfel încât fiecare nod să fie
considerat o singură dată. Prin parcurgerea unui arbore se determină o ordine totală asupra nodurilor
din arbore. În acest sens, pentru fiecare nod se poate spune care nod îl precede și care îl succede.

Arborii sunt structuri neliniare. Implementarea lor se poate face prin structuri de liste sau prin
structuri cu legături fiu -frate . Accesul la elementele arborelui se face prin intermediul unei variabile
care să indice rădăcina sa. Să observăm că astfel arborele oarecare se transformă într -un arbore binar.

128

Figura 1-2

Figura 1-2 indică reprezentarea arborelui oarecare de mai sus, sub forma unui arbore cu legături fiu –
frate. Legătura fiu a unui nod face legătura cu primul fiu al nodului. Legătura frate face legătura c u
următorul frate (fiu al aceluiași nod).

În implementarea prin intermediul unei structure de liste adăugarea unui nod se face ca ultim fiu al
unui nod deja existent, pentru care se indică nodul tată. Pentru identificarea nodului tată este necesară
parcur gerea arborelui. Aceasta se face cu ajutorul unei stive pentru adresele nodurilor la care nu s -a
utilizat legătura stângă.

Parcurgerea în preordine presupune utilizarea informației utile urmată de parcurgerea în inordine a
subarborilor fiilor. La parcurg erea în postordine se vizitează mai întâi în postordine subarborii fii, apoi
se utilizează informația utilă.

Se poate evita utilizarea explicită a stivei prin folosirea recursivității într -o reprezentare ca arbore
însăilat. Nodurile unui arbore însăilat c onțin un câmp suplimentar cu valoarea 1, dacă legătura este una
normală, respectiv 2, dacă legătura este de însăilare. Într -o altă variantă se poate modifica semnificația
câmpului tipn așa încât să ia valorile:
1 pentru câmp de informație utilă în care llfiu este o legătură la lista de fii,
2 pentru câmp din lista de fii în care lnext este o legătură la următorul fiu,
3 pentru câmp de informație utilă în care llfiu este o legătură de însăilare,
4 pentru câmp din lista de fii în care lnext este o legătură de însăilare.

În implementarea bazată pe legături fiu -frate , în câmpurile care alcătuiesc structura nodurilor sunt data
pentru informația utilă, lfiu pentru legătura către primul fiu al nodului și lfrate pentru legătura către
următorul frate al nodului c urent. Accesul la oricare dintre nodurile arborelui se face prin intermediul
rădăcinii R, și efectuând o parcurgere până la idendificarea nodului sau până când nu s -au până la

129
parcurgerea tuturor nodurilor. Principiile de creare și parcurgere a arborilor p rezentate anterior se
adaptează noii modalități de stocare. Evitarea utilizării unei stive suplimentare se poate face și în acest
caz prin tehnica însăilării.

1.6 Arbori binari, arbori binari de cǎutare

Se numește arbore binar un arbore în care fiecare nod are cel mult doi fii, unul stâng și unul drept, și
dacă un nod are un singur fiu, se precizează dacă este fiu stâng sau drept. În Error! Reference source
ot found. este prezentat un arbore binar în care nodurile 1, 6 și 7 au numai fiu drept, iar nodul 5 are
numai fiu stâng.

Accesul la elementele arborelui binar se face pornind de la rădăcină, a cărei adresă se memorează ( în
R, pentru exemplele de mai jos). Pentru configurarea structurii unui nod vom considera:
node->inf = valoarea datelor stocate în elementul nod,
node->llink = adresa fiului stâng,
node->rlink = adresa fiului drept.

Asupra elementelor unui arbore binar pot fi impuse anumite proprietăți, obținându -se astfel arbori
binari cu proprietăți aparte. Efectuarea de operații (adăugarea/extragerea unui element, concatenarea,
etc.) asupra arborilor trebuie să păstreze proprietățile acestora și acest lucru este verif icat dacă,
parcurgând arborele, ordinea nodurilor este păstrată.

Figura 1-3

Se numește arbore binar strict un arbore binar care are proprietatea că fiecare nod, cu excepția
nodurilor terminale, are exact doi descendenți.

Se numește arbore binar complet un arbore binar strict care are toate nodurile terminale pe același
nivel.

130
Un arbore binar este echilibrat pe înălțime dacă este vid, iar dacă nu e vid, atunci subarborii stâng și
drept trebuie să fie echilibrați și diferența înălțimilor lor să fie cel mult 1.

Modalități de parcurgere:
 preordine – se utilizează informația din rădăcină, se parcurge în preordine subarborele
stâng, apoi se parcurge în preordine subarborele drept;
 inordine – se parcurge în inordine subarborele stâng, se utilizează informația din rădăcină,
apoi se parcurge în inordine subarborele drept;
 postordine – se parcurge în postordine subarborele stâng, se parcurge în postordine
subarborele drept, apoi se utilizează informația din rădăcină.

Implementarea parcurgerilor descrise anterior se poate face atât iterativ cât și recursi v. Implementarea
iterativă necesită gestionarea prin program a unei stive suplimentare pentru memorarea nodurilor care
nu au fost încă prelucrate. Spre deosebire de aceasta, în implementarea recursivă gestionarea stivei
revine sistemului, scrierea algoritm ului devenind mult mai compactă. Pentru exemplificare indicăm
implementarea recursivă și iterativă pentru parcurgerea în preordine, adaptarea lor pentru celelalte
două tipuri de parcurgeri fiind evidentă.

Implementarea recursivă a parcurgerii în preordine :

preordine ( node )
{
if (node != NULL)
{
write(node ->inf) //sau altă prelucrare a inf din
node
preordine (node->llink)
preordine (node->rlink)
}
}
parcurgerea arborelui făcându -se cu apelul:
preordine ( R )

Implementarea iterativă a parcurgerii în preordine :
S = Φ;
if (R != NULL) R => S;
while (S != Φ)
{
x <= S;
write(x->inf)
if (x->llink != NULL) x->llink => S;
if (x->rlink != NULL) x->rlink => S;
}
unde am considerat S stivă ce conține adresele nodurilor din arbore ăn ordinea în care se vor introduce.

Pentru compactarea scrierii algoritmului am folosit notațiile:
x => S pentru introducerea unui nod în stivă

131
x <= S pentru extragerea unui nod din stivă
S = Φ pentru inițializarea stivei

Eliminarea utilizării unei stive pentru parcurgerea arborelui se face dacă, în locul uneia din legături la
unul din fii, se face trimitere către nodul care urmează conform modului de parcurgere ales pentru
arbore. O asemenea legătură se numește de însăilare , iar arborele , binar (simplu) însăilat . O
modalitate de identificare a legăturilor de însăilare constă în a adăuga încă un câmp, tiprl, structurii
nodurilor arborelui, care ia valoare 1 dacă rlink este o legătură normală și 0 dacă rlink este o legătură
de însăilare.

Se poate imagina și o implementare cu utilizarea ambelor legături pentru însăilare, arborele obținut
fiind arbore binar dublu însăilat . Identificarea legăturilor de însăilare se poate face prin adăugarea de
câmpuri tipll și tiplr care să indice tipul legăturii stângi, respectiv, drepte. O altă modalitate este de a
utiliza un singur câmp suplimentar tipl cu valorile:
0 pentru ambele legături efective,
1 pentru legătura llink de însăilare și rlink normală,
2 pentru llink legătură normală, iar rlink de însăilare,
3 pentru cazul utilizării ambelor legături la însăilare.

1.7 Sortarea și cǎutarea

În cele ce urmează vom prezenta principiile celor mai populari algoritmi de sortare, precum și o în
variantă în psudocod pentru fiecare. Aplicarea lor se consideră pentru vectori de întregi aflați în
memoria internă. Cel mai adesea, în compararea diverșilor algoritmi se are în vedere complexitatea
lor. În practică această informație nu reprezintă neapărat un criteriu de decizie asu pra algoritmului care
trebuie folosit pentru problema dată și, în niciun caz nu este singurul criteriu de luat în considerare.
Aspectele care sunt luate în considerare pentru evaluarea unui algoritm de sortare țin de: stabilitate
(elementele cu chei egale să nu fie reordonate), spațiu suplimentar necesar (ideal O(1)), număr maxim
de comparări (ideal O(n ln n)), număr maxim de comutări de două valori între ele (ideal O(n)),
adaptiv (ideal O(n) când datele sunt aproape sortate, sau sunt puține chei unice). V om arăta
caracteristicile generale ale fiecăruia și vom face, de asemenea, precizări cu privire la apli cabilitatea
lor pentru sortări externe.

Pe lângă bibliografia clasică specificată la sfârșitul acestui capitol, pentru caracterul lor practic, am aut
în vedere și descrierile de la https: //www.toptal.com/developers/sorting -algorithms , precum și
http://quiz.geeksforgeeks.org/ pentru exemplificările de funcționare a algoritmilor. De remarcat
prezentarea care găsește la https://en.wikipedia.org/wiki/Sorting_algorithm , succintă dar bine
întocmită, bogată în informații și însoțită de tabele comparative pentru un număr important de
algoritmi de sortare.

Concluziile ar fi următoarele: comple xitatea nu este un factor de decizie în alegerea unui algoritm,
fiecare dintre aceștia având avantaje și dezavantaje, astfel că nu există unul cel mai bun, ideal pe toate
aspectele menționate, condițiile inițiale date de ordonarea datelor și de distribuție a cheilor afectând
substanțial funcționarea. Ca atare, alegerea algoritmului adecvat ține de aplicație și de caracteristicile
datelor asupra cărora trebuie să funcționeze algoritmul.

132
1.7.1 Sortarea prin numărare

Sortarea prin numărare presupune ca pentru fiecare element al vectorului v să se numere câte valori
mai mici sau egale decât acesta sunt. Acestea vor indica și poziția pe care se va afla fiecare element în
vectorul ordonat w, și se memorează într -un vector p, adică y[p[i]]=x[i].
for(i=0; i < n; i++)
p[i]=0;
for(i=1; i<n; i++)
for(j=0; j<i; j++)
if(x[i] <= x[j])
p[j]++;
else
p[i]++;
for(i=0; i<n; i++)
y[p[i]]=x[i] ;

Astfel, plecând de la vectorul 7 5 2 4 3 9, obținem:

0 1 2 3 4 5
v – inițial 7 5 2 4 3 9
p 4 3 0 2 1 5
v – final 2 3 4 5 7 9

De observat c ă, la aplicarea algoritmului pentru sortare externă, pentru fiecare comparație se fac 3
operații I/O, de unde rezultă un număr de operații de transfer de ordinul O(n2), ceea ce -l face de
nerecomandat pentru sortări externe.

1.7.2 Sortarea prin inserție

Principiul: în orice moment, elementele vectorului de sortat v, sunt ordonate până pe poziția i -1;
elementul de pe poziția i este inserat între acestea, după ce s -au făcut comparații succesive, cu
decalarea elementelor mai mari la dreapta, cu o unitate. O descriere în pseudocod este:
insertionSort(int v[], int n)
{
int i, j, x;
for (i = 1; i < n; i++)
{
x = v[i];
j = i-1;
while (j >= 0 && v[j] > x)
{
v[j+1] = v[j];
j = j-1;
}
v[j+1] = x;
}
}

133

Prezentăm, în tabelul de mai jos, evoluția vectorului 7 5 2 4 3 9 pentru primii trei pași ai algoritmului:

i j 7 5 2 4 3
9
1 0 5 7 2 4 3
9
2 1 5 2 7 4 3
9
0 2 5 7 4 3
9
3 2 2 5 4 7 3
9
1 2 4 5 7 3
9
0 2 4 5 7 3
9

Se observă că, pe cazul cel mai nefavorabil (v ordonat descrescator), se fac n(n –1)/2 comparații și 3
operații de acces la vectorul x pentru fiecare comparație. Daca v este uniform distribuit, atunci
numărul mediu de comparații este ((n –1)+n(n –1)/2) /2, așadar o complexitate de comparare O(n2).

Să observăm stabilitatea algoritmului, faptul că nu necesită spațiu suplimentar pentru ordonare,
precum și adaptabilitatea lui (dacă vectorul este aproape sortat, timpul este O(n)). Astfel, în ciuda
complexității sale, O(n2), el este utilizat pentru vectori de dimensiune mică, sau pentru date despre
care se știe că sunt aproape sortate.

O variantă optimizată a sortării prin inserție este sortarea prin inserție binară, în care identificarea
poziției p e care trebuie inserat elementul i printre elementele de pe pozițiile 0…i -1 se face printr -o
căutare binară.

Datorită numărului mare de accesări ale vectorului, și pe poziții aleatoare, algoritmul nu se pretează la
o aplicare în cazul sortării externe.

1.7.3 Sortarea prin metoda bulelor

Principiul: dacă două elemente alăturate nu sunt în ordinea bună se face inversarea lor, după cum
urmează :
bubbleSort(int v[], int n)
{ // n dimensiunea vectorului de sortat
int i, j, comuta;
for (i = 0; i < n -1; i++)
{
comuta = 0;
for (j = 0; j < n -i-1; j++)
if (v[j] > v[j+1])
{

134
v[j] <-> v[j+1];
comuta = 1;
}
// daca nu au mai fost elemente care sa comute
// s-a terminat ordonarea
if (!comuta)
break;
}
}

Algoritmul face ca elementul cu valoarea cea mai mare să coboare cel mai repede pe poziția sa, iar
cele ușoare să urce, precum bulele de aer într -un pahar cu apă; de unde și denumirea metodei. De
observat că algoritmul este stabil și adaptiv (O(n) când este aproape s ortat), precum și faptul c ă nu
folosește spațiu suplimentar. Complexitatea este, însă, O(n2).

Prezentăm, în tabelul de mai jos, evoluția vectorului 7 5 2 4 3 9 pentru primii doi pași ai algoritmului:

i j 7 5 2 4 3
9
0 0 5 7 2 4 3
9
1 5 2 7 4 3
9
2 5 2 4 7 3
9
3 5 2 4 3 7
9
4 5 2 4 3 7
9
1 0 2 5 4 3 7
9
1 2 4 5 3 7
9
2 2 4 3 5 7
9
3 2 4 3 5 7
9

În [2], D. Knuth conchide prin a preciza că, “bubble sort pare să nu aibă nimic care să îl recomande, cu
excepția unui nume antrenant și a faptului că conduce la unele problem teoretice interesante”, pe care
le discută.

1.7.4 Sortarea prin interclasare

Înțelegem prin i nterclasare (en. merge ) procesul de obținere din dou ă sau mai multe structuri de
același tip, având aceleași proprietăți, a unei structuri cu aceleași proprietăți [1]. Avem în vedere aici
structuri liniare (vectori, liste, stive, cozi). Astfel, de exemplu, interclasarea a n liste dublu înlănțuite,

135
cu valori ordonate crescător, va duce la crearea unei liste dublu înlănțuite, c e conține toate elementele
acestora, și ele sunt ordonate crescător.

Interclasarea a doi vectori
Prezentăm ideea generală de interclasare a a doi vectori ordonați, v și w, rezultatul fiind pus într -un al
treilea vector, r.
interclasare(int v[], int w[], i nt r[], int lv, int lw, int lr)
{
int i=0, j=0, k=0;

//se compara pe rand valorile vectorilor v si w
//valoarea cea mai mica se trece in r
//se avanseaza in r si in vectorul din care s -a copiat
valoarea
while (i < lv && j < lw)
r[k++] = v[i] < w[j] ? v[i++] : w[j++];

//copierea elementelor ramase
while (i < lv)
r[k++] = v[i++];
while (j < lw)
r[k++] = w[j++];
}

Sortarea prin interclasare (en. merge sort ) este o metoda de sortare recursivă în care:
 vectorul inițial este împărțit în doi vectori,
 pentru fiecare dintre aceștia se aplică algoritmul de sortare,
 vectorii sortați se interclasează,

Dacă st și dr sunt capătul stâng, respectiv, drept al vectorului de sortat, v, atunci algoritmul de sortare
l-am putea scrie astfel:
mergeSort(int v [], int st, int dr)
{
if (st < dr)
{
int m = st+(dr -l)/2;
mergeSort(v, st, m);
mergeSort(v, m+1, dr);

merge(v, st, m, dr);
}
}

apelul făcându -se cu :
mergeSort(v, 0, dimensiune_v – 1);

136
Funcția merge apelată face interclasarea vectorilor ordonați v[st … m] și v[m+1 … dr], subvectori ai
vectorului v[st … dr], iar rezultatul este pus tot în v. Implementarea propusă mai jos folosește un
vector suplimentar b de dimensiune m -st+1, inițializat cu v[st … m].
void merge(int v[], int st, int m, int dr)
{
int i, j, k;
if(st < dr)
{
int b[m-st+1];
for(i=st, j=0; i<=m; i++, j++)
b[j]=v[i];
i = 0; j = m+1; k = st;
while(i <= m -st && j <= dr)
v[k++] = (v[j]<b[i])?v[j++]:b[i++];
while(i <= m -st)
v[k++]=b[i++];
}
}

Să observăm stabilitatea algoritmului și faptul că spațiul suplimentar este O(n) în implementări tipice.
Astfel, sortarea prin inserție este folosită l a sortarea listelor înlănțuite în O(n ln n), precum și în sortări
externe, deoarece accesul la date nu se face aleator, ci succesiv.

O aplicare a algoritmului pe vectorul: 4 3 9 5 2 7 duce la următoarea evoluție a acestuia:

Figura 1-4

137
1.7.5 Sortarea rapidă

Principiul:
 se alege un pivot (există diferite implementări ale alegerii pivotului),
 se așează pivotul pe poziția pe care o va avea în vectorul ordonat,
 elementele mai mici se pun în stânga pivotului ia r cele mai mari în dreapta sa,
 vectorii stâng și drept se ordonează cu aceeași metodă.

O transpunere în pseudocod în care v este vectorul de ordonat și st, dr marginile sale, ar fi :
quickSort(int v[], int st, int dr)
{
if (st < dr)
{
int pivot = partitionare(v, st, dr);
quickSort(v, st, pivot – 1);
quickSort(v, pivot + 1, dr);
}
}

Unde partitionare(v, st, dr) returnează valoarea pivotului, după ce în prealabil pe poziția respectivă a
pus elementul care se va afla în vectorul ordonat. În exemplul de mai jos se alege ca pivot elementul
de pe ultima poziție din vector, se plasează pe p oziția lui corectă, astfel încât în stânga sa sunt valorile
mai mici, respectiv în dreapta sa, valorile mai mari decât el, ale vectorului v.
partitionare (int v[], int st, int dr)
{
int pivot = v[dr];
int i = st – 1, j;
for (j = st; j < d r; j++)
if (v[j] <= pivot)
{
i++;
v[i] <-> v[j]; // interschimbare
}
v[i + 1] < -> v[dr]);
return (i + 1);
}

Deși de aceeași complexitate cu sortarea prin interclasare, pe cazul vectorilor sortare a rapidă este
preferată pentru faptul că sortarea se face fără spațiu adițional, ceea ce grăbește procesul. În cazul
listelor înlănțuite însă, situația este inversată, deoarece interclasarea fiind mai eficientă datorită
parcurgerii secvențaiale a nodurilor față de multele comutări care au loc în cazul sortării rapide și care
sunt costisitoare pentru cazul listelor.

1.7.6 Căutarea

Căutarea secvențială este modalitatea de aflare a unei valori date într -o structură liniară prin
parcurgerea acesteia element cu element. Căutarea se încheie dacă elementul a fost găsit sau dacă nu a
fost găsit dar s -a parcurs întreaga structură. Numărul mediu de comparații es te (n+1)/2. În cazul în care

138
structura poate fi cu cheie multiplă (valorile elementelor se pot repeta), căutarea poate presupune și
identificarea pozițiilor pe care se găsește valoarea căutată.

Căutarea în vectori ordonați se mai numește și căutare binară , principiul fiind de înjumătățire, la
fiecare pas, a dimensiunii vectorului în care se caută, prin compararea valorii de căutat cu valoarea de
la jumătatea vectorului și, dacă nu a fost identificată, să se continue căutarea în prima jumătate dacă
cheia e mai mică, respectiv în cea de a două jumătate dacă cheia e mai mare. Complexitatea este dată
de numărul maxim de pași ai algoritmului, O( ln n).

Căutarea în arbori binari . Pentru eficientizarea căutării într -un arbore de căutare, acesta se
construiește ast fel încât pentru fiecare nod, orice cheie din subarborele stâng are valoare mai mică
decât nodul și orice cheie din subarborele drept are valoare mai mare decât nodul. Un astfel de arbore
se numește arbore binar de căutare .

Parcurgerea în inordine a unui arbore binar de căutare determină prelucrarea nodurilor în ordinea
crescătoare a valorilor lor.

Căutarea unui element dat se face prin compararea valorii cu rădăcina arborelui. Dacă nu coincid,
căutarea va continua în subarborele stâng sau drept după cum valoarea căutată este mai mică sau mai
mare decât rădăcina. Căutarea continuă până când elementul căutat este găsit sau s -a ajuns pe NULL.
Complexitatea este O(ln n).

2. Înțelegerea conceptelor

1. Fie A o matrice cu 20 de linii și 17 coloane, stocată într -un vector obținut prin linearizarea linie a
acesteia. Determinați rangul elementului A(3,4) în cadrul acestui vector.
Indicație . Se numără elementele matricii A, pe linii, care se pun în vector înaintea elementului A(3,4).
Rangul său în cadrul vectorului este : 17*2+4 -1=37.

2. Fie A o matrice cu 20 de linii și 17 coloane. Determinați rangul elementului A(3,4) în cadrul
vectorului obținut prin linearizare linie a matricii A știind că aceasta este:
a. superior triunghiulară,
b. superior 3 -diagonală
și stocarea se face optimal.
Indicație . Nu se vor stoca în vector elementele care sunt nule pe forma matricii (deci nu cele de sub
diagonala principală, respectiv nu cele de deasupra și de dedesubtul celor trei diagonale). Pentru cazul
(a) rangul se obți ne din 17+16+2 -1=34, iar pentru (b) din 2+3+2 -1=6.

3. Se dau doi vectori ale căror elemente sunt ordonate crescător. Scrieți un algoritm de interclasarea a
acestora. Comentați eficiența algoritmului, ca timp de execuție și spațiu suplimentar necesar.
Indicaț ie. A se vedea secțiunea despre sortarea prin inserție unde este descris și algoritmul de inserare.

4. Scrieți algoritmul de inserare a unui element x pe o poziție dată, pos, în cadrul unei liste
implementate secvențial.
Indicație. Într-o implementare secvențială, elementele listei se stochează în ordine, ca elemente ale
unui vector. Inserarea presupune eliberarea poziției dorite cu deplasarea cu o poziție la dreapta a
tuturor elementelor din listă începând de pe acea poziție. Primu l element al vectorului se află pe

139
poziția 0. Considerăm că variabila MAX indică numărul actual de elemente al listei. (Poziția ultimului
element al listei din cadrul vectorului va fi MAX -1.) Faptul că lista este vidă îl indicăm prin MAX=0 .

if ( pos <= MA X)
{
for (i = MAX; i >= pos ; i –)
arr[i] = arr[i -1];
arr[i] = x ;
MAX = MAX+1;
};
else overlist ; // pozitia nu e in lista

5. Scrieți algoritmul de eliminare dintr -o listă implementată secvențial a elementului de pe poziția,
pos.
Indicație . Vedeți indicația de la problema 6 și țineți cont să: verificați existența unui element pe poziția
indicată, să faceți deplasările la stânga cu o unitate a elementelor ce succed elementului scos și să
actualizați dimensiunea listei.

6. Indicați algoritmii de introducere, respectiv extragere a unui element în/dintr -o coadă secvențială
în care a[0] reprezintă vârful cozii, iar P indică baza ei.
Indicație . [1], p. 48.

7. Să se reprezinte evoluția unei stive asupra căreia se efectuează următoarele operații, știind că
inițial ea este vidă:
a. se introduc, în ordine: 7, 15, 23, 18;
b. se extrag trei elemente;
c. se introduc: 12, 28, 15;
d. se extrage un element.
Indicație . Principiul de funcționare al unei stive este LIFO (ultimul element care intră în stivă este
primul care iese). Astfel, într -o reprezentare pe orizontală a stivei, în care baza este elemental cel mai
din stânga iar vârful este elemental cel mai din dreapta, evoluția stivei este: 7, 15, 23, 18; 7; 7, 12, 28,
15; 7, 12, 28.

8. Să se reprezinte evoluția unei cozi asupra căreia se efectuează următoarele operații, știind că inițial
ea este vidă:
a. se introduc, în ordine: 7, 15, 23, 18;
b. se extrag trei elemente;
c. se introduc: 1 2, 28, 15;
d. se extrage un element.
Comentați situațiile în care coada este simplu înlănțuită, dublu înlănțuită, circulară simplu înlănțuită,
respectiv circulară dublu înlănțuită.
Indicație . Principiul de funcționare al unei cozi este FIFO (primul element ca re intră în coadă este
primul care iese). Astfel, într -o reprezentare orizontală a cozii, în care baza este elementul cel mai din
dreapta (pe unde se adaugă elemente la coadă) iar vârful este elementul cel mai din stânga (pe unde se
extrag elemente din co adă), evoluția este următoarea: 7, 15, 23, 18; 18; 18, 12, 28, 15; 12, 28, 15.
Forma (circulară sau nu) și tipul de înlănțuire (simplă sau dublă) nu impietează asupra evoluției cozii.

9. Implementați adunarea polinoamelor de o singură variabilă folosind reprezentarea acestora ca
liste.
Indicație . Fiecare element al listei conține un termen al polinomului. Configurația unui nod este:

140
termen->coeficient = coeficientul termenului,
termen->putere = puterea termenului,
termen->next = adresa elementului următor.
Termenii polinomului apar în listă în ordinea descrescătoare a puterilor. Considerăm P și Q pointeri
către două polinoame în forma descrisă anterior. P va rămâne neschimbat iar Q va conține suma celor
două polinoame.

Q1 = Q; P1 = P;
while ((P1 != NULL) && (Q1 != NULL))
{
if (Q1->putere > P1->putere)
Q1 = Q1->next;
if (Q1->putere == P1->putere)
{
Q1->coeficient = Q1 ->coeficient + P1 ->coeficient;
Q1 = Q1->next; P1 = P1 ->next;
}
if (Q1->putere < P1->putere)
{
PP = P1; PP->next = Q1 ->next; Q1 ->next = PP;
Q1 = PP->next;
}
}
if (Q1 == NULL)
while (P1 != NULL)
{
Q1 = P1;
P1 = P1->next;
}

10. Cum poate fi determinat, în mod optimal, cel mai mare element al unui arbore binar de cǎutare?
Justificați.
Indicație . Datorită felului ordonării nodurilor din arborele binar de căutare, cel mai mare element este
cel mai din dreapta element, plecând din rădăcină. Astfel, determinarea lui se va face plecând din
rădăcină, pe legătura dreaptă, câtă vreme aceasta nu este NULL.

11. Care este înǎlțimea maximǎ, respectiv minimă, pe care o poate avea un arbore binar de căutare cu
nodurile: 1, 42, 35, 140, 16, 127, 2? Justificați răspunsul. Dați câte un exemplu pentru fiecare caz.
Indicație . Înălțimea maximă se obține dacă pe fiecare n ivel avem câte un singur nod; sunt 7 noduri,
deci înălțimea maximă este 6. Înălțimea minimă se obține pentru un arbore echilibrat; sunt 7 noduri,
deci înălțimea minimă este 2 (3 nivele, arbore complet echilibrat).

12. Se numește arbore binar strict un arbore binar care are proprietatea că fiecare nod, cu excepția
nodurilor terminale, are exact doi descendenți. Calculați numărul total de noduri ale unui arbore
binar strict care are n noduri terminale.
Indicație . Fie N numărul total de noduri al arborelui (terminale și neterminale), n i numărul de noduri
terminale de pe nivelul i, respectiv k i numărul de noduri neterminale de pe nivelul i. Avem relația de
recurență k i + n i = 2k i-1. Se obține N=2n -1.

13. Se numește arbore binar complet un arbore binar strict car e are toate nodurile terminale pe același

141
nivel. Să se determine numărul total de noduri ale unui arbore binar complet cu n noduri
terminale.
Indicație . Arborele binar complet este un caz particular de arbore binar strict, deci se poate aplica
rezultatul ( cu raționamentul) de la problema precedentă. O altă rezolvare se poate da ținând cont de
forma particulară a arborilor binar compleți rezultată din definiția lor (inducție după numărul de noduri
terminale, astfel, trecerea de la un arbore binar complet cu n noduri terminale la un arbore binar
complet cu mai multe noduri terminale, înseamnă adăugarea a încă unui nivel cu 2n frunze, deci 2n
noduri terminale).

14. Să se indice toți arborii binari de căutare ale caror noduri conțin numerele: 21, 32, 93, 46.
Indic ație. Cu 4 noduri se pot construi 24 de topologii de arbori binari. Parcurgerea în inordine va
indica nodurile în ordine crescătoare dacă arborele este binar de căutare. Avem, deci, 24 de arbori
binari de căutare. Se dau câteva exemple, se indică modalitat ea de determinare a tuturor configurațiilor
posibile.

15. Fie un arbore binar de căutare care conține numere cuprinse între 1 și 1000. Poate fi următoarea
secvență: 927, 220, 903, 254, 890, 258, o secvență de căutare a valorii 258 în cadrul arborelui dat?
Indicație . Valorile indicate fiind obținute prin căutare în arbore binar de căutare, și ținând cont de
algoritmul specific acestei căutări, ele ar trebui să ne conducă la reconstruirea unui fragment de arbore
binar de căutare. Se verifică acest lucru.

16. Implementați un arbore binar cu n noduri. Să se scrie o procedură de parcurgere în preordine a
arborelui, cu afișarea pe ecran a nodurilor parcurse.
Indicație . Se scriu procedurile de creare a unui nod și de parcurgere în preordine, într -una din
variantele descrise în secțiunea referitoare la arbori. Se apelează de n ori crearea nodului; se apelează
parcurgerea în preordine.

17. Cum se reprezintă într -o structură de date un arbore binar însăilat? Exemplificați pe un arbore în
care însăilarea are în vedere parcu rgerea în preordine.
Indicație . A se vedea secțiunea precedentă.
18. Cum se reprezintă arborii de expresii într -o structură de date?
Indicație . Nodurile arborilor de expresii conțin operatori, respectiv operanzi (constante sau variabile).
Fiecare nod ce conține un operator are tot atâția fii cât este gradul operatorilor. Pentru evaluarea
expresiei se face evaluarea recursivă a subarborilor rădăc inii.

19. Aplicați algoritmul de sortare prin inserție vectorului 35, 7, 4, 8, 32, 6. Determinati ordinea
elementelor vectorului după efectuarea celui de al treilea pas din execuția algoritmului.
Indicație . În prezentarea teoriei din capitolul precedent se af lă și descrierea evoluției unui vector la
aplicarea primilor pași ai algoritmului.

20. Explicați evoluția unui vector dat la aplicarea unui algoritm de sortare dat.
Indicație . După cum se fac descrierile în partea de teorie din secțiunea precedentă, cu explic area
principiului de funcționare al algoritmului sau cu descrierea acestuia.

21. Ce se înțelege prin căutare secvențială? Care este numărul mediu de comparări într -o căutare
secvențială?
Indicație . Căutarea secvențială înseamnă căutarea unei anumite valori într -un vector oarecare, printr -o
parcurgere a acestuia de la primul până la ultimul element, sau până când elementul dat este găsit.
Numărul mediu de comparații este (n+1)/2, pentru un vector cu n elemente (1 comparație dacă
elementul este pe prima poziție, n dacă este pe ultima poziție; sau n(n+1)/2n prin luarea în considerare

142
a tuturor variantelor de identificare a valorii căutate, adică tot (n+1)/2).

22. Descrieți un algoritm de sortare pentru o listă înlănțuită.
Indicație . Algoritmul de sortare prin inserție este ușor de adaptat într -o implementare pe liste. Pentru
performață în stabilitate se recurge la sortarea prin interclasare ( en. merge sort).

23. Explicați tipurile de liste înlănțuite și moda litatea în care acestea se parcurg.
Indicație . Tipurile de liste înlănțuite sunt: simplu înlănțuite (fiecare nod are o informație utilă și o
informație de legătură către nodul următor, respectiv NULL pentru legătura ultimului nod), dublu
înlănțuite (cu exc epția nodurilor extreme, fiecare nod, pe lângă informația de stocat are o legătură la
nodul următor și o legătură la nodul precedent; nodurile din capete au una din legături pe NULL),
circulare (listă înlănțuită al cărei ultim nod are legătură către primul nod). Pentru lista simplu înlănțuită
se face pe baza câmpului de legătură, pornind de la primul nod până la NULL. Traversarea listei dublu
înlănțuite se face în ambele sensuri. Parcurgerea listei circulare este similară traversării listei simplu
înlănțuit e, oprirea terminându -se, însă, la întâlnirea nodului din vârf.

24. Indicați caracteristicile algoritmului de sortare cu bule și a algoritmului de sortare rapidă:
principiul de funcționare, comlpexitate, utilizare.
Indicație . Răspunsul se găsește în secțiunea dedicată descrierii algoritmilor de sortare menționați.

25. Indicați algoritmii de sortare rapidă și de sortare prin inserție. Exemplificați folosind același
vector de start.
Indicație . Se explică pas cu pas principiile celor doi algoritmi și se analizează e voluția vectorului pe
fiecare dintre aceștia, așa cum este indicat și în capitolul teoretic.

3. Aplicarea și utilizarea noțiunilor teoretice

1. Fie A o matrice cu 10 linii și 10 coloane, stocată într -un vector obținut prin linearizarea linie a
acesteia. Determinați:
a. Numărul de elemente ale vectorului.
b. rang(A(3,7)) în cadrul vectorului.
c. rang(A(3,7)) în cadrul vectorului, știind că A este superior triunghiulară și stocarea s -a
făcut optimal.
Indicație . a. Coincide cu numărul de elemente al matricii. b. Prin numărarea elementelor de pe liniile
anterior stocate și cele de pe linia a treia, avem: 10+10+7 -1=26. c. 10+9+5 -1=23.

2. Fie v vectorul care stoch ează cifrele ce alcătuiesc CNP -ul dumneavoastră, în ordinea din CNP.
Presupunând că acesta reprezintă linearizarea linie a unei matrici superior k -diagonale, să se
determine această matrice. Indicați toate soluțiile posibile.
Indicație . Vectorul fiind obți nut din linearizarea unei matrici superior k -diagonale, celelalte elemente
ale matricii sunt 0 și, mai mult, sunt ignorate, a.î. elementele vectorului ocupă în întregime una sau
mai multe diagonale ale matricii. Dimensiunea maximă a matricii este 13 (număr ul de cifre din CNP).
Mai Dimensiunea minimă este 7 (sunt 7 elemente pe diagonala principală și 6 pe una din cele două
diagonale adiacente).

3. Scrieți algoritmul care inversează ordinea nodurilor unei liste circulare simplu înlănțuite. [3] (dar
se găseșt e în numeroase variante și cu diferite propuneri de rezolvare pe internet)

143
Indicație . Inversarea ordinii nodurilor presupune inversarea legăturilor dintre noduri. Accesul la listă
se face prin intermediul capului listei ( head ). El va fi capul listei și în lista finală. Varianta algoritmică
avută în vedere consideră un pointer x pentru parcurgerea listei, acesta fiind nodul a cărui legătură își
schimbă direcția. Se folosesc încă două variabile auxiliare, p pentru predecesorul lui x, respectiv q
pentru succe sorul lui în lista inițială.

x = head ->next; p = head; // initializarile pentru parcurgere
head->next = NULL; // marcarea capatului listei
while(x)
{
q = x->next; // q este succesorul lui x
x->next = p; // schimbarea sensului legăturii lui x
p = x; x = q; // deplasarea cu un nod în lista inițiala
}
head = p;

4. Indicați o modalitate de reprezentare a unei liste circulare înlănțuite astfel înc ât lista să poată fi
traversată eficient în ambele sensuri, fiecare celulă având un singur câmp de legătură. [2] (Vol. 1,
pr. 18). Descrieți algoritmi de inserare și ștergere a unui element.
Indicație . Câmpul de legătură al fiecărui nod conține diferența adreselor nodurilor succesor și
predecesor ale acestuia. Memorând adresele a două noduri succesive se pot deduce adresele tuturor
celorlalte elemente și, astfel, parcurgerea listei.

5. Descrieți o modalitate de implementare a mulțimilor precum și algoritmi de operare asupra
acestora (reuniune, intersecție, diferență).
Indicație . În [1], p.111, este propusă următoarea abordare. Mulțimile se memorează ca vectori ale
căror elemente sunt ordonate strict crescator. Pentru a obține reuniunea a două mulțimi se face o
parcurgere a celor doi vectori (v și w în care se stochează cele două mulțimi ) ca pentru interclasarea
lor, însă, în vectorul rezultat elementele identice se iau o singură dat. Pentru operația de intersecțe se
vor pune în vectorul rezultat numai elementele identice, iar pentru diferență numai acele elemente care
sunt în primul vect or dar nu și în al doilea. Pentru algoritmul de interclasare a doi vectori a se vedea
metoda indicată la secțiunea despre sortarea prin interclasare.

6. Să se afișeze în ordine inversă valorile stocate într -o listă înlănțuită.
Indicație . O soluție ar fi util izarea unei stive suplimentare, S, în care se stochează elementele scoase pe
rând din stivă. Golirea ulterioară, cu afișare, a stivei S, face ca elementele să apară invers decât în listă.

while (L != Φ)
{
x <= L;
x => S;
}
while (S != Φ)
{
x <= S;
write(x);
}

7. Să se ordoneze crescător un șir de numere, stocat ca listă (L), folosind două stive (A, B).

144
Indicație . Stiva A este cea care va conține numerele ordonate crescător de la vârf la bază. Se scoate un
element x din listă. Se varsă în stiva B toate elementele din A care sunt mai mici decât x. Se introduce
x în A. Se varsă B în A. Se continuă până se videază L și toate elementele sunt în A. O transpunere în
pseudocod a metodei de mai sus este:

while (L != Φ)
{
x <= L;
while (V A < x)
{
y <= A;
y => B;
}
x => A;
while (B != Φ)
{
y <= B;
y => A;
}
}

8. Se dau dou ă liste (L 1, L2) ale căror elemente sunt ordonate crescător. Să se obțină o singură listă
care conține toate elementele celor două liste, ordonate crescător.
Indicație . O modalitate de rezolvare presupune utilizarea a două stive suplimentare, S 1 și S2, în care se
golesc listele L 1, respectiv L 2. Apoi se parcurg simultan cele două stive prin compararea vârfurilor lor.
Se extrage cel cu valoarea mai mare și se introduce în L 1. Procesul se încheie după ce s -au golit
ambele stive. Lista L 1 finală corespunde cerințelor problemei.

9. Se consideră o coadă cu elemente numere întregi. Scrieți algoritmul care elimină din coadă toate
numerele pare.
Indicație . Considerăm A coada inițială; folosim și o coadă auxiliară B, inițial vidă. Extragem pe rând
toate elementele din A și, pe cele care sunt impare, le punem în coada B. După vidarea cozii A, se
golește coada B în A, așa încât A să conține toate elementele impare din coada A inițială, în ordinea în
care erau.

10. Determinați câți arbori binari se pot construi cu trei noduri distincte.
Indicație . Sunt 5 topologii posibile. Pe fiecare din acestea se valorile pot fi puse în 3! moduri
(permutări de 3 elemente). Total: 5*6=30 arbori binari.

11. Determinați câți arbori binari de căutare se pot construi cu trei noduri distincte.
Indicație . Pe fiecare din cele 5 topologii, valorile celor trei noduri au o unică aranjare pentru ca
arborele să fie arbore binar de căutare. Răspuns: 5 arbori bi nari de căutare.

12. Scrieți algoritmul care permite compararea a doi arbori binari de căutare.
Indicație . Doi arbori pot să difere prin topologie și/sau prin valorile nodurilor. Doi arbori sunt egali
dacă pentru fiecare nod valorile nodurilor coincid, și sun t egali atât cei doi subarbori stângi cât și cei
drepți. Dacă r1 și r2 sunt rădăcinile celor doi arbori de comparat algoritmul de verificare egal(r1,r2)
este:

145
int compara_arbori_binari (nod *r1, nod *r2)
(
if(r1 == NULL) then return r2 == NULL;
if(r2 == NULL) then return r1 == NULL;
return r1->data == r2 ->data &&
egal(r1->st, r2->st) &&
egal(r1->dr, r2-> dr);
}

Funcția se apelează furnizând ca parametri adresele rădăcinilor celor doi arbori. Ea returnează 1 sau 0
după cum cei doi arbori sunt identici s au nu.

13. Se consideră arborele binar din Figura 3-1, cu f, g, h funcții binare, m, n, p funcții unare, și x,
y, z variabile. Scri eți expresia care caracterizează acest arbore binar, obținută prin parcurgerea sa
în preordine.

Figura 3-1
Indicație . fgmxnyhpzy

14. Fie f, g, h, i funcții binare, m, n funcții unare și x, y, z variabile. Reprezentați sub forma unui
arbore binar expresia f(g(h(x,y),z),m(i(x,n(z)))) . Precizați tipul de parcurgere al
arborelui expresiei de mai sus și realizați implementarea acestuia.
Indicație . Se face o parcurgere în preordine a arborelui din Figura 3-2. Fiul unui nod ce
corespunde unei funcții unare este fiu stâng.

146

Figura 3-2

15. În următoare expresie aritmetică apar doar operatori binari: a+(b+c)/2*(d -e). Realizați
arborele binar asociat acesteia. Parcurgeți arborele precizând tipul parcurgerii.

Figura 3-3
Indicație . Error! Reference source not found. conține arborele binar asociat expresiei date.
arcurgerea în: postordine abc+2/+de -* ; preordine *+a/+bc2 -de; inordine a+b+c/2*d -e.

16. Scrieți algoritmul care determi nă optimal cel mai mare/mic element al unui arbore binar de

147
cǎutare. Justificați.
Indicație . Consecință directă a definiției arborelui binar de căutare, cel mai mare element este cel mai
din dreapta element al său. Pentru a -l determina, se parcurge arborele plecând din rădăcină și mergând
pe legătura din dreapta a fiecărui nod ș.a.m.d. până când legătura este NULL . Nodul respectiv are cea
mai mare cheie. Pentru nodul cu cheia cea mai mică se face un raționament similar pentru cel mai din
stânga nod.

17. Se organizează un campionat de tenis cu 8 jucători, pe sistemul cine pierde un meci iese din
comp etiție. Meciurile dintre jucătorii rămași se stabilesc prin tragere la sorți. Toate meciurile
dintre jucătorii rămași în competiție se joacă într -o singură zi. Să se determine numărul total de
meciuri și câte zile va dura campionatul. Generalizare.
Indicaț ie. Se observă construirea unui arbore binar strict, complet de adâncime 3. Campionatul
durează 3 zile. Generalizarea presupune intrarea în competiție a unui număr de jucători n=2k, caz în
care campionatul va dura k zile.

18. Scrieți un algoritm de determinare a înălțimii unui arbore binar.
Indicație . Algoritmul se bazează pe recursivitatea definiției arborelui binar. Înălțimea fiind dată de
distanța de la rădăcină la cea mai îndepărtată frunză, vom determina înălțimea arborelui ca: 1 +
max( înălțimea _ subarborelui_ stâng, înălțimea_ subarborelui_ drept). Iar arborele vid are înălțime 0.

19. Scrieți un algoritm de determinare a numărului frunzelor unui arbore binar.
Indicație . Algoritmul se bazează pe recursivitatea definiției arborelui binar, ceea ce fa ce ca numărul
frunzelor arborelui de rădăcină r să fie suma dintre numărul frunzelor subarborelui stâng și numărul
frunzelor subarborelui drept. Funcția care implementează algoritmul returnează numărul calculat.

int nr_frunze(nod *r)
{
if(r == NULL) return 0;
else if((r ->st == NULL) && (r ->dr == NULL)) return 1;
else return nr_frunze(r ->st)+nr_frunze(r ->dr);
}

20. Scrieți un algoritm care verifică dacă un arbore binar este echilibrat pe înălțime (arborele vid este
echilibrat, iar dacă nu e vid, subarborii stâng și drept trebuie să fie echilibrați și diferența
înălțimilor lor să fie cel mult 1).
Indicație . Funcția următoare primește ca argument adresa rădăcinii arborelui; ea returnează 1 dacă
arborele este echilibrat și 0 dacă nu e echilibrat.

int este_echilibrat(nod *r)
{
int st_h, dr_h; // inaltimile subarborelui stang, respectiv
drept
if (r == NULL) return 1; //arborele vid este echilibrat

//recuperarea inaltimii celor doi subarbori
//vezi o problema anterioara pentru algoritm
st_h = inal time(r->st);
dr_h = inaltime(r ->dr);
if ( abs(st_h -dr_h) <= 1 &&
este_echilibrat(r ->st) &&
este_echilibrat(r ->dr))

148
return 1 ;

//daca s -a ajuns aici, arborele nu este echilibrat
return 0;
}

21. Scrieți algoritmul de sortare prin inserție. Studiați evoluției vectorului cu elementele: 8 , 22, 4, 7, 3,
10 la aplicarea acestui algoritm.
Indicație . A se vedea secțiunea corespunzătoare descrierii algoritmului. Evoluția vectorului: pas 1 – 8,
22, 4, 7, 3, 10; pas 2 – 4, 8, 22, 7, 3, 10; pas 3 – 4, 7, 8, 22, 3, 10; pas 4 – 3, 4, 7, 8, 22, 10; pas 5
– 3, 4, 7, 8, 10, 22.

22. Același enunț ca la problema anterioară pentru fiecare dintre algoritmii de sortare studiați.
Indicație . A se vedea secțiunea corespunzătoare descrierii fiecărui algoritm.

23. Aplicarea algoritmilor de sortare pe vector de numere naturale pentru trierea acestora în numere
pare și, respectiv, impare (la final vectorul va conține în prima parte numai numere pare, apoi
numai numere impare).
Indicație . În oricare din algoritmii de sortare, comparările cu int erschimbare se finalizează dacă
elementul de indice mai mare este par și cel de indice mai mic este impar.

24. Se dă un vector sortat cu n elemente. Scrieți algoritmul de identificare în acest vector a unui
element cu valoare precizată și determinați complexi tatea lui.
Indicație . Vezi secțiunea precedentă pentru descrierea metodei căutării binare.

25. Care dintre metodele de sortare următoare este mai rapidă: sortarea prin inserție (en. insertion
sort), sortarea rapidă (en. quick sort), sortarea prin metoda bule lor (en. bubble sort), sortarea
binară (en. binary sort), sortarea lineară (en. linear sort), sortarea prin combinare (en. merge sort)?
De ce? Ce ați alege pentru implementarea sortării unei liste? Comentați.
Indicație . Cea mai bună metodă de sortare este considerată a fi quick sort, atât ca timp cât și ca spațiu
suplimentar necesar, complexitatea fiind O(n log n). Criteriile care sunt de luat în considerare la
alegerea unei metode au în vedere mai multe aspecte: stabil itate (nu se reașează chei cu aceeași
valoare), spațiu suplimentar necesar, complexitate, număr maxim de comutări, comportament pe
situații extreme (datele sunt aproape ordonate, sunt puține chei unice, datele sunt aleatoare). La modul
general, nu se poate afirma că există un cel mai bun algoritm de sortare. Merge sort este bună pentru
memorie limitată. Sortarea prin selecție este mai bună decât metoda bulelor. De asemenea, ulterior
alegerii unui algoritm sau altul, trebuie avut în vedere faptul că și calit atea implementării poate aduce
îmbunătățiri asupra timpului de execuție, în cadrul aceluiași tip de sortare. De aceea, atunci când există
biblioteci care implementează o metodă de sortare, acestea sunt de preferat, pentru optimalitatea
implementării lor. O analiză comparativă mai detaliată a diverșilor algoritmi de sortare se găsește la
https://www.toptal.com/developers/sorting -algorithms.

149
Bibliografie

1. 1. S. Bârză și L. -M. Morogan, Structuri de date , București: Ed. FRM, 2007.
2. 2. D. E. Knuth, Arta programării calculatoarelor , Vol.1 și 3, Ed. Teora, 2000, 2001.
3. 3. I. Tomescu, Data Structures , Bucharest Univ. Press, 1997, 2004.
4. 4. D. D. Burdescu și M. C. Mihăescu, Structuri de date și algoritmi. Aplicații , Craiova: Ed. Sitech,
2007.
5. 5. T. H. Cormen, C. Leiserson și R. Rivest, Introducere în algoritmi , Cluj -Napoca: Ed. Computer
Libris Agora, 2000.

150

151
IV . Sisteme de operare
Grigore Albeanu , Prof. univ. dr., Universitatea SPIRU HARET
Alexandru Averian , Senior developer, Luxsoft România

1. Organizarea structurală a sistemelor de calcul. Fundamente

Tehnica de calcul a evoluat de la abac până la supercalculatoarele actuale (sisteme hipercub, sisteme
multi -nucleu, sisteme cloud/fog ș.a.). Etapele de dezvoltare a calculatoarelor electronice sunt
cunoscute sub numele de generații de calculatoare. Până în prezent au fost parcurse cinci generații
[2]: generația tuburilor electro nice (programe binare sau cablate, suport informațional: cartela
perforată sau banda de hârtie perforată), generația tranzistorilor și diodelor (apar suporturile
magnetice, apar limbajele de programare: FORTRAN: eng. FOR mula TRAN slation , COBOL: eng.
COmmon Business -Oriented Language , ALGOL: eng. ALGO rithmic Language ș.a., apar primele
sisteme de operare), generația circuitelor integrate (apare conceptul de firmware , apar sisteme de
operare evoluate rezidente pe disc (DOS: eng. Disk Operating System ), apar noi limbaje de
programare: PL/1, Pascal, LISP: eng. LISt Processing , BASIC), generația VLSI : eng. Very Large
Scale Integration (apar microprocesoarele, noi tipuri de arhitecturi: microcalculatoarele (de exemplu
cele compatible IBM PC – eng. Persona l Computer , stațiile MAC etc.), sisteme de operare precum
CP/M, DOS (de la IBM sau Microsoft); se dezvoltă rețelele de calculatoare; apar limbajele de
programare orientată spre obiecte) și generația inteligenței artificiale (în continuă dezvoltare) cu
trimitere către viitoarele sisteme neurale, sisteme cuantice, calcul ADN etc.

Una dintre componentele principale ale oricărui sistem de calcul o reprezintă procesorul. Procesorul
actualelor sisteme poate fi de tip special, un microprocesor sau un ansamblu int egrat de
(micro)procesoare.

Orice procesor conține patru blocuri funcționale de bază: unitatea de comandă și control (UCC),
unitatea aritmetico -logică (UAL), registrele procesorului, unitatea de interfață cu celelalte componente
(UI). Procesoarele perfor mante utilizează structuri de date avansate precum stivele. Acestea sunt utile
pentru salvarea contextului unei activități înainte de întreruperea acesteia. Primele trei componente
formează unitatea de executare. UCC comandă, coordonează și controlează înt reaga activitate de
prelucrare la nivelul componentelor calculatorului. Ea va executa instrucțiunile unui program. UAL
realizează prelucrarea datelor cerută prin instrucțiuni: operații aritmetice, logice, de comparare etc.

Registrele reprezintă o memorie foarte rapidă a procesorului în care se păstrează codul instrucțiunilor,
datele de prelucrat, rezultatele prelucrărilor etc. Cele mai importante registre ale unui procesor sunt:
registrul acumulator , registrul numărător de adrese al programului , registrul indicatorilor de condiții
(valoare negativă, pozitivă sau nulă, transport în urma executării operațiilor de calcul etc.), registrul de
instrucțiuni și registrul de adresare a memoriei . În general, registrul acumulator păstrează unul dintre
operanzii unei i nstrucțiuni de calcul, fiind totodată și destinația rezultatului operației. Registrul
numărător de adrese al programului sau registrul contor -program, arată adresa, în memoria internă,
unde se află stocată următoarea instrucțiune de executat. Indicatorii de condiție sunt poziționați
automat în urma efectuări anumitor operații. Registrul de instrucțiuni memorează instrucțiunea ce se
execută. Conținutul acestui registru este analizat pentru a se determina operația de executat, locul unde
se află stocați ope ranzii precum și locul unde va fi stocat rezultatul, dacă instrucțiunea este una de
calcul, respectiv adresa unde se va face un salt în program sau adresa unei zone de memorie unde/de
unde se va stoca/citi o anumită dată, în alte situații. Registrul de adr esare a memoriei păstrează adresa

152
curentă folosită pentru efectuarea accesului la memorie. De obicei, adresa efectivă se obține în urma
unui calcul de adresă.

UI asigură legătura dintre procesor și celelalte componente ale calculatorului îndeplinind func ția de
transfer de date de la/spre procesor. Comunicarea procesorului cu celelalte componente: adaptorul
video, adaptorul de disc etc. se face prin intermediul porturilor (puncte de intrare în procesor). Acestea
pot fi porturi de intrare (vin date de la co mponente) respectiv porturi de ieșire (pornesc date spre
componente). În practică, un port este identificat printr -un număr (unic).

Deoarece un sistem de calcul execută mai multe activități, acestea pot avea nevoie de controlul
procesorului. Rezultă nece sitatea întreruperii unei activități pentru a trece la o altă activitate. Aceste
comutări sunt determinate fie prin hardware, fie prin software. Întreruperea hardware este declanșată la
apariția unui semnal de întrerupere prin care procesorului i se cere s ă analizeze un anumit eveniment.
După ce au fost inițiate de către procesor, unele activități se pot desfășura fără controlul procesorului,
de exemplu modul de prelucrare DMA (eng. Direct Memory Accesss .)

Stocarea informațiilor în sistemul de calcul se realizează prin intermediul memoriei. Memoria este
spațiul de lucru primar al oricărui sistem de calcul. În funcție de locul ocupat, distingem: memoria
centrală (numită și memoria principală sau internă ) și memoria secundară (numită și auxiliară sau
secundară ). În memoria centrală sunt stocate programele și informațiile utilizate de ele în timpul
execuției lor de către procesor. Memoria secundară păstrează cantități mari de date și programe
folosite frecvent ș i încărcabile rapid în memoria centrală. Memoria unui sistem de calcul este
caracterizată prin: capacitate , timp de acces , viteză de transfer , cost, mod de accesare a informației
stocate etc.

Totalitatea echipamentelor unui sistem de calcul diferite de un itatea centrală și memoria internă
formează mulțimea echipamentelor periferice. Din această categorie fac parte unitățile de memorie
externă, echipamentele de intrare, echipamentele de ieșire și echipamentele de intrare/ieșire. Alte
echipamente sunt destin ate redării unor aspecte ale realității virtuale : mănușa de date (eng: data
glove ), casca VR (HMD: Head Mounted Display ), camera VR (CAVE: Cave Automatic Virtual
Environment ) etc.

Sistemele de calcul pot fi de tip numeric (digitale), analogic și de tip hi brid. Calculatoarele numerice,
sunt cele care primesc, prelucrează și transmit date/informații codificate binar. Ele fac obiectul acestei
lucrări. Sistemele de calcul analogice sunt echipamente alcătuite din amplificatoare operaționale și
circuite numerice independente, interconectate în vederea realizării unor operații de tip continuu:
integratoare, multiplicatoare etc. Mărimile corespunzătoare condițiilor inițiale se introduc sub forma
unor tensiuni electrice. Acestea sunt prelucrate și se obțin noi tensi uni electrice ce vor fi vizualizate cu
ajutorul unor dispozitive speciale. Sistemele hibride sunt rezultatul cuplării unor sisteme numerice cu
sisteme analogice. Comunicarea între cele două tipuri de sisteme se realizează prin intermediul unor
dispozitive de conversie: analogic -digital; digital -analogic. Sistemele analogice nu fac obiectul acestei
lucrări.

O clasificare interesantă a sistemelor digitale a fost propusă de Flynn (1972) și cuprinde atât sistemele
de calcul cu o singură unitate centrală, cât ș i pe cele cu mai multe unități centrale. Clasificarea lui
Flynn consideră ca esențiale două caracteristici: mărimea fluxului instrucțiunilor și mărimea fluxului
datelor. Un sistem de calcul cu flux unic de instrucțiuni și flux unic de date este numit siste m de calcul
SISD (eng. Single Instruction Single Data Stream ). Toate sistemele de calcul tradiționale (cu o singură
unitate centrală) sunt mașini SISD. Această categorie include sisteme de calcul, de la
microcalculatoarele personale până la calculatoarele multiutilizator de mare putere (eng. mainframe ):
IBM/704, IBM/7040, IBM 360/40, IBM 370/135, PDP, FELIX C, microcalculatoarele IBM PC etc.

153
Următoarea categorie o reprezintă sistemele de calcul cu flux simplu de instrucțiuni, dar cu flux
multiplu de date, n umite sisteme SIMD (eng. Single Instruction Multiple Data Stream ). Aceste sisteme
se bazează tot pe o singură unitate centrală cu o singură unitate de comandă, dar cu N elemente de
prelucrare (UAL) și N module de memorie atașate acestora, toate interconectate (N 2). Unitatea de
comandă emite instrucțiuni sub formă de flux unic spre toate elementele de prelucrare, în mod
simultan. La un moment dat, toate elementele de prelucrare active execută aceeași ins trucțiune, numai
asupra datelor situate în propriul modul de memorie, deci flux multiplu de date. Din această clasă fac
parte unele supercalculatoare. Sistemele SIMD sunt, la rândul lor, de mai multe categorii: a)
matriceale – prelucrează datele în mod pa ralel și le accesează prin adrese în loc de index și valoare ; b)
cu memorie asociativă – operează asupra datelor accesate asociativ (prin conținut). În loc de adresă,
specificarea datelor se face prin valoare, cum ar fi: "mai mare decât", "mai mic decât", "între limitele",
"egal cu" etc.; c) matriceal -asociative – sunt sisteme de tip asociativ ce operează asupra tablourilor
multidimen sionale (matrice și masive de date); d) ortogonale – fiecare element procesor corespunde la
un cuvânt ( 32 sau 64 de biți) de memorie și, astfel, biții de același rang ai tuturor cuvintelor pot fi
prelucrați în paralel. Acest procedeu mai este numit procesare serială pe bit și paralelă pe cuvânt .
Clasa sistemelor cu flux multiplu de instrucțiuni și flux unic de date MISD (eng. Multiple Instructions
Single Data Stream ) include acele structuri specializate ce folosesc mai multe fluxuri de instrucțiuni
executate pe același flux de date. Ultima categorie o reprezintă sistemele MIMD (eng. Multiple
Instructions, Multiple Data Stream ), ce reprezintă un grup de calculatoare independente, fiecare cu
propriul context (program, date etc.). Multe dintre supercalculatoare și toate sistemele distribuite intră
în această clasă. Sistemele MIMD pot fi divizate în două categorii: sistemele multip rocesor (cu
memorie comună) și sisteme multicalculator . Pe de altă parte, fiecare din aceste clase se poate împărți
în funcție de modul de interconectare. Există două posibilități de interconectare: magistrală (similar
televiziunii prin cablu) și comutație (similar rețelei telefonice). Se obțin astfel patru clase de sisteme
MIMD (figura 3.3): sisteme multiprocesor cu magistrală, sisteme multiprocesor comutate, sisteme
multicalculator cu magistrală ( rețele de calculatoare ) și sisteme multicalculator comutate (sisteme
distribuite generale ).

Calculatoarele dintr -o rețea pot fi de același tip (rețele omogene) sau de tipuri diferite (rețele
eterogene) . Rețelele de calculatoare permit folosirea în comun a unor resurse fizice scumpe
(imprimante, discuri fixe de ma re capacitate etc.) și folosirea în comun a unor date. Datele care se
schimbă între sisteme de calcul se numesc documente electronice . În funcție de aria de răspândire a
calculatoarelor dintr -o rețea, se disting următoarele tipuri de rețele:
1. Rețele locale – LAN (eng. Local Area Network ): În aceste rețele, aria de răspândire nu
depășește 2 km și deservesc o societate comercială. Rețelele locale sunt formate de regulă din
calculatoarele aflate într -o clădire sau un grup de clădiri.
2. Rețele metropolitane – MAN (eng. Metropolitan Area Network ): Aceste rețele acoperă
suprafața unui oraș.
3. Rețele globale – WAN (eng. Wide Area Network ): Calculatoarele acestor rețele au o arie
geografică de răspândire foarte mare (rețele internaționale, "Internet" etc.)
Avanta jele strategice în mediul de afaceri/educațional includ: facilitarea comunicațiilor în cadrul unei
firme/școli, creșterea competitivității firmei/instituției, posibilitatea organizării resurselor în grupuri de
lucru cu efect asupra reducerii bugetelor afec tate prelucrării datelor. Din punct de vedere operațional
și/sau tactic se remarcă: reducerea costurilor per utilizator, creșterea siguranței serviciilor de calcul
(prin posibilitatea includerii serverelor “în oglindă“ (eng. mirror )), îmbunătățirea adminis trării
software -ului, îmbunătățirea integrității datelor (datele de pe server vor fi salvate în mod regulat),
îmbunătățirea timpului de răspuns (într -o rețea necongestionată). Un utilizator al unei rețele locale poate
avea următoarele avantaje: mediul de c alcul poate fi ales de către utilizator, crește repertoriul de
aplicații, crește securitatea informației (sistemul de operare în rețea fiind cel care
restricționează/permite accesul la datele rețelei), există posibilitatea instruirii on -line (prin intermed iul
bazelor de date pentru instruire) etc.

154

Din punct de vedere software, accesul la blocurile de instrucțiuni specializate în comanda și controlul
unităților de disc, imprimantelor și a altor periferice este asigurat de către sistemul de operare al rețelei
(NOS – eng. Network Operating System ). Un N OS este un software de rețea ce permite rețelei să
suporte capabilități multiproces (eng. multitasking ) și multiutilizator. De asemenea un NOS oferă
posibilități deosebite pentru partajarea resurselor și pentru comunicare.

În acest moment se afl/a in con tinu/a dezvoltare sisteme MIMD de tip cloud. Fog computing este o
nou/a paradigm/a ce faciliteaz/a leg/atura cu sistemele cloud a componentelor ce fac partea din
Internetul lucrurilor (Internet of Things).

2. Structura sistemelor de operare

Un sistem de operare este "o colecție organizată de programe de control și serviciu, stocate permanent
într-o memorie principală sau auxiliară, specifice tipurilor de echipamente din componența unui sistem
de calcul, având ca sarcini: optimizarea utilizării resurselor , minimizarea efortului uman de
programare și automatizarea operațiilor manuale în cât mai mare măsură, în toate fazele de pregătire și
execuție a programelor". Sistemul de operare pune la dispoziția utilizatorilor (operatori, programatori
etc.) și o inter față concretizată într -un interpretor al comenzilor utilizatorului exprimate cu ajutorul
unui limbaj de comandă . Toate sistemele de operare moderne (UNIX/Linux, IOS, Windows etc.) oferă
și o interfață grafică, comenzile fiind selectate din meniuri ierarhic e folosind dispozitive de
interacțiune grafică sau tastatura. Totuși, puterea limbajului de comandă nu poate fi atinsă numai cu
ajutorul elementelor grafice.

Interfața dintre sistemul de operare și programele utilizatorului (numite și lucrări – eng. jobs) este
asigurată de o colecție de "instrucțiuni extinse" ale sistemului de operare numite apeluri sistem .
Folosind apeluri sistem (organizate în biblioteci statice (.lib) sau dinamice (.dll)), un program utilizator
poate crea, utiliza și șterge diverse obie cte gestionate de către sistemul de operare.

Cele mai importante obiecte gestionate de un sistem de operare modern sunt procesele și fișierele . De
asemenea, firele de executare (eng. threads ) sunt obiecte specifice programării concurente (de
exemplu folosind limbajul Java), dar și ca modalitate de implementare a proceselor Windows prin
programare multi -fir (eng. multi -thread programming ).

Procesul (eng. task) reprezintă conceptul cheie al or icărui sistem de operare. Un proces este o entitate
dinamică care corespunde unui program în execuție . Procesele sunt fie procese sistem, fie procese
utilizator. Procesele sistem sunt asociate unor module ale sistemului de operare, iar procesele utilizator
sunt asociate unor programe utilizator care pot să creeze alte procese utilizator sau să lanseze pentru
executare procese sistem. Un proces (numit proces tată ) poate creea unul sau mai multe procese
(numite procese fiu sau descendenți ). În sistemele multi utilizator fiecare proces este caracterizat de
identificatorul proprietarului (utilizatorului).

Un fișier (eng. file) este un șir de caractere terminat printr -o marcă de sfârșit de fișier (EOF – eng. End
Of File). Fișierul poate fi stocat pe disc, în memo rie etc. Una din funcțiile importante ale unui sistem
de operare este aceea de a “ascunde” dificultatea lucrului cu echipamentele periferice. Astfel, sistemul
de operare oferă apeluri sistem pentru lucrul cu fișiere: creare, ștergere, citire, scriere etc. Fișierele pot
fi grupate într -un catalog (eng. directory, folder ) și pot fi caracterizate de anumite atribute (proprietar,
dimensiune, data ultimului acces în scriere, coduri de protecție etc.).

155

Modulele software pentru tratarea cererilor de intrare/ieșir e de către sistemul de operare se numesc
drivere . Fiecare dispozitiv periferic are asociat un driver. În general, orice driver menține o coadă a
cererilor de intrare/ieșire lansate de unul sau mai multe procese și pe care le prelucrează într -o anumită
ordine (în funcție de momentul lansării cererii sau conform unei liste a priorităților). Un driver este
răspunzător de satisfacerea cererilor de transfer de informație, de tratarea erorilor ce pot apărea la
realizarea unei operații fizice, ș.a. Un program util izator poate efectua operații de intrare/ieșire la
nivelul unui driver, totuși programul său nu mai este independent de dispozitiv. De aceea, pentru
operațiile de intrare -ieșire se utilizează apelurile sistem sau diferitele proceduri specializate puse la
dispoziție de mediile de programare.

Primele sisteme de operare realizau prelucrarea pe loturi de programe (eng. batch mode ). Utilizatorul
nu comunica direct cu sistemul de calcul; acesta funcționa sub controlul unui operator uman
specializat. Operatorul avea sarcina de a asigura resursele externe necesare unei lucrări (montarea
benzilor magnetice, pornirea și oprirea diverselor echipamente periferice). De asemenea, operatorul
asigura și introducerea lucrărilor în sistem. Comunicarea operațiilor de executa t, se realiza prin
intermediul unei interfețe de tip alfanumeric ce utiliza un limbaj de comandă pentru descrierea
ordinelor adresate sistemului, precum și pentru specificarea acțiunilor necesare tratării erorilor.
Primele sisteme de acest tip funcționau î n regim de monoprogramare , un singur program fiind încărcat
în memorie la un moment dat. Caracteristica de bază a acestui mod de prelucrare o reprezintă
imposibilitatea intervenției utilizatorului pentru a interacționa cu programul său. Dintre conceptele
implementate pentru creșterea performanțelor și mărirea eficienței utilizării resurselor sistemelor de
calcul, un rol important l -a avut multiprogramarea . În sistemele cu multiprogramare, la un moment
dat, în memorie se află încărcate, pentru executare, mai multe procese (programe în executare), ce
concurează, pe baza unei scheme de priorități, pentru accesul la anumite resurse ale sistemului. Când o
resursă este retrasă unui proces (la încheierea acestuia sau la apariția unui proces cu prioritate mai
mare), aceasta poate fi imediat alocată unui proces solicitant. Sistemele cu multiprogramare sunt din
ce în ce mai complexe. Ele au de rezolvat probleme dificile privind: alocarea optimă a resurselor,
evitarea interblocărilor, protecția utilizatorilor (între ei) și protecția sistemului (în raport cu
utilizatorii).

În sistemele uniprocesor, execuția mai multor programe în regim de multiprogramare pare simultană
din punctul de vedere al utilizatorului, dar la un moment dat, există doar un singur proces activ în
sistem. Totuși, în sistemele multiprocesor sau multicalculator, două sau mai multe procese pot fi active
simultan, ele fiind prelucrate de procesoare diferite. Un alt mecanism important este multiprocesarea .
Acesta constă în multiprogramarea a două sau mai m ulte procese având un obiectiv comun. Într -un
sistem de operare multiproces (eng. multitasking ) procesele pot comunica între ele și își pot sincroniza
activitățile. Sistemele Microsoft bazate pe tehnologia NT, sistemele UNIX și Linux sunt sisteme de
operar e multiproces. Conceptul de memorie virtuală este, de asemenea, foarte important în contextul
sistemelor de operare moderne.

Mulțimea funcțiilor și modul de realizare a acestora definesc caracteristicile unui sistem de operare .
Aceste caracteristici pot f i utilizate pentru a clasifica și compara sistemele de operare. Cele mai
importante caracteristici sunt: i) modul de introducere a programelor în sistem [introducere serială,
paralelă, respectiv la distanță]; ii) modul de planificare a proceselor [orientar e pe lucrări; orientare pe
proces]; iii) numărul de programe prezente simultan în memorie [monoprogramare; multiprogramare];
iv) modul de utilizare a resurselor [alocare completă; timp real; partajare]; v) numărul de utilizatori ce
pot folosi sistemul în a celași timp [monoutilizator; multiutilizator].

Referitor la modul de utilizae a resurselor, când resursa partajată este timpul unității centrale, sistemul
de operare devine cu timp partajat (eng. time sharing ). În general, sistemele de operare din această

156
clasă asigură utilizarea eficientă a resurselor sistemelor de calcul conversaționale în care accesul la
resursele sistemului poate fi:
a) direct – pentru asigurarea unui control direct și permanent asupra unui set de terminale pe baza
unui program utilizator; un caz particular al acestor sisteme îl reprezintă sistemele interactive
în timp real, în cadrul cărora se cere o valoare maximă prestabilită a timpului de răspuns;
b) multiplu – pentru accesul simultan, al u nui număr mare de utilizatori, la resursele hardware și
software ale sistemului; acest tip de acces apare când sunt cel puțin două terminale în sistem,
iar fiecare utilizator lucrează cu programul său într -o regiune de memorie (partiție) diferită de
regiu nile celorlalți utilizatori, protejată printr -un mecanism software sau hardware;
c) în timp partajat (time-sharing ) – în care alocarea timpului de acces se realizează pe baza unor
cuante de timp fixe sau variabile, de ordinul milisecundelor, utilizatorii avâ nd impresia că
lucrează simultan cu sistemul;
d) la distanță – în care prelucrarea se produce de către mai multe calculatoare asupra datelor care
sunt distribuite în mai multe colecții de date dispersate geografic (eng. distributed data bases ).

Un sistem de operare, în forma cea mai simplă, apare ca o colecție de proceduri cu funcții precise și cu
o interfață bine precizată între acestea. Serviciile (apelurile sistem) furnizate de sistemul de operare,
sunt solicitate prin încărcarea anumitor registre cu info rmația necesară sau depunerea acestei
informații în stivă și apoi provocarea unei întreruperi cunoscută sub numele de apel supervizor sau
apel nucleu (eng. Kernel). Acest apel determină trecerea sistemului de calcul din modul utilizator în
modul nucleu (su pervizor) și transferă controlul sistemului de operare. Sistemul de operare analizează
parametrii apelului pentru a -l identifica, iar apoi apelează procedura de serviciu necesară. Această
descriere sugerează scheletul unui sistem de operare [1]:
 un progra m ce invocă o procedură de serviciu;
 bibliotecă de proceduri de serviciu ce corespund apelurilor sistem;
 bibliotecă de proceduri utilitare pentru procedurile de serviciu.

O altă posibilitate este de a privi sistemul de operare structurat pe niveluri, un nivel peste alt nivel. De
exemplu, un sistem de operare cu 6 niveluri poate cuprinde (din interior spre exterior): alocarea
procesorului și multiprogramarea, gestiunea memoriei, comunicația operator -sistem, gestiunea
echipamentelor de intrare/ieșire, progr amele utilizator și operatorul.

Pentru a -și îndeplini funcțiile, majoritatea sistemelor de operare sunt structurate pe două straturi:
stratul logic și stratul fizic . Stratul logic asigură interfața dintre utilizator și sistem (prin comenzi,
instrucțiuni extinse și mesaje). Stratul fizic este reprezentat de rutinele de tratare a întreruperilor
software. El este apropiat de hardware și, în general, este transparent pentru utilizator.

Tendința în realizarea sistemelor de operare moderne este de a implementa cea mai mare parte a
funcțiilor sistemului de operare sub formă de procese utilizator. Pentru a solicita un serviciu, un proces
utilizator (numit și proces client) emite o cerere unui proces de serviciu, care rezolvă solicitarea și
transmite clientului ră spunsul. Această comunicație între clienți și procesele de serviciu este asigurată
de nucleul sistemului de operare.

Exemple de sisteme de operare:
 MSDOS – monolitic, nestructurat
 Windows XP – arhitectura stratificată
 BSD Unix , Solaris – arhitectura mod ulară
 True 64 UNIX, QNX – mikrokernel
 Linux – artitectura monolitica
 Mach – microkernel

157
 Mac OS X – stratificat, modular
 Minix – microkernel

3. Gestiunea proceselor
3.1. Fundamente

Deoarece resursele unui sistem de calcul sunt, în cele mai multe cazuri, limitate cantitativ, este
imposibilă alocarea tuturor resurselor necesare unui proces la crearea acestuia. Astfel, la un moment
dat, este posibil ca un proces să nu mai poată continua . Imposibilitatea continuării apare și atunci când
un proces trebuie să aștepte producerea unui eveniment. De exemplu, dacă un proces trebuie să
genereze date necesare altui proces, acesta din urmă va aștepta până când datele necesare vor fi
disponibile. A tunci când un proces se află în imposibilitate de a continua deoarece nu dispune de
resursele necesare, se spune că se află în starea BLOCAT . În general, un proces ajunge în această
stare atunci când din punct de vedere logic el nu mai poate continua. Totu și, este posibil ca un proces
să fie conceptual gata pentru executare , dar să fie stopat deoarece sistemul de operare a decis alocarea
procesorului unui alt proces (cu prioritate mai mare). În această situație se spune că procesul este
ÎNTRERUPT . Cele două condiții sunt complet diferite. În primul caz, suspendarea este inerentă (de
exemplu, se așteaptă introducerea unei linii de la tastatură). A doua situație este de natură tehnică (nu
poate avea fiecare proces procesorul/nucleul său). Dacă procesul este în curs de executare și deci în
mod implicit dispune de toate resursele necesare, se spune că procesul se află în starea ACTIV . În
sistemele de calcul cu un singur procesor, la un moment dat, numai un singur proces poate fi activ . În
sisteme de calcul multip rocesor pot fi active cel mult atâtea procese câte procesoare/nuclee există în
configurație.

Din punct de vedere practic, un proces constă dintr -un program și contextul acestuia. Contextul
procesului cuprinde toate informațiile externe programului, ce pot fi adresate de acesta și care uneori
sunt strict necesare evoluției procesului. Aceste informații sunt grupate într -o structură numită teoretic,
vector de stare al procesului , iar practic bloc de control al procesului . Acest bloc de control conține
inform ații precum: identificatorul procesului, starea procesorului (contorul program, conținutul
registrelor, controlul întreruperilor etc.) – când procesul nu este activ, starea procesului (ACTIV ,
ÎNTRERUPT , BLOCAT etc.), prioritatea procesului la obținerea res urselor, codurile de protecție,
tabela de translatare a memoriei virtuale, informații despre alte resurse (fișiere, dispozitive periferice
etc.) alocate procesului și informații privind contabilizarea resurselor (timpul de utilizare al
procesorului, memori a ocupată, numărul operațiilor de intrare -ieșire). Mulțimea câmpurilor conținute
în tabela de procese diferă de la un sistem de operare la altul.

Crearea unui proces înseamnă: alocarea unui vector de stare, inițializarea componentelor vectorului de
stare și înregistrarea procesului într -o structură de date numită tabela proceselor . Astfel fiecare proces
are un descriptor unic și anume: poziția în tabela proceselor. Prin acest identificator sistemul de
operare și alte procese îl pot referi pe toată durata l ui de viață. Un proces creat la cererea altui proces
utilizator sau sistem (numit proces părinte ), se numește proces fiu și se spune că este un descendent al
procesului creator. Dacă două procese B și C sunt create de același proces A, atunci B și C se num esc
procese frați. Dacă un proces fiu creează un alt proces, acesta din urmă este și descendent al părintelui
procesului care l -a creat. Mulțimea proceselor create de un proces A, a proceselor create de aceste
procese ș.a.m.d, formează descendența procesul ui A. În acest fel toate procesele sistemului alcătuiesc
o structură arborescentă.

158
Distrugerea sau ștergerea unui proces înseamnă eliberarea resurselor ce i -au fost alocate, eliberarea
memoriei corespunzătoare vectorului de stare, precum și eliminarea ace stuia din tabela proceselor. Un
proces se poate autodistruge, atunci când ajunge la sfârșitul executării sale sau, poate fi distrus la
cererea unui alt proces sistem sau utilizator.

Alte operații asupra proceselor sunt: semnalizarea, întârzierea, planific area, schimbarea priorității,
suspendarea și reluarea. Un proces poate fi semnalizat în legătură cu apariția unui eveniment sau poate
fi întârziat până la apariția unui eveniment. Pentru a obține controlul unui procesor, procesele sunt
planificate. Schimba rea priorității unui proces influențează modul de alocare a resurselor necesare
procesului. Un proces ÎNTRERUPT (respectiv BLOCAT ) poate fi ÎNTRERUPT -SUSPENDAT
(respectiv BLOCAT -SUSPENDAT). Prin reluare, un proces BLOCAT -SUSPENDAT (respectiv
ÎNTRERUPT -SUSP ENDAT ) devine BLOCAT (respectiv ÎNTRERUPT ). Tranzițiile posibile ale
unui proces pe timpul duratei sale de viață sunt [1]:
 ÎNTRERUPT –> ACTIV : Această tranziție este realizată prin operația de planificare în
următoarele condiții: procesul are prioritate maximă, procesorul (sau unul dintre procesoare)
este liber și procesul are alocate toate resursele necesare intrării în executare.
 ACTIV –> ÎNTRERUPT : Tranziția are loc când un proces cu prioritate mai ma re decât
procesul în executare a ajuns în starea ÎNTRERUPT și are nevoie de procesor. Operația ce
cauzează tranziția este cea de planificare .
 ACTIV –> BLOCAT : Această tranziție are loc când procesul ACTIV așteaptă apariția unui
eveniment. Astfel, procesorul ocupat de procesul ACTIV devine disponibil pentru un alt
proces aflat în starea ÎNTRERUPT .
 BLOCAT –> ÎNTRERUPT : Trecerea în starea ÎNTRERUPT are loc când evenimentul
așteptat de procesul BLOCAT se pro duce. Această tranziție se produce în urma operației de
semnalizare .
 ÎNTRERUPT –> ÎNTRERUPT -SUSPENDAT : Tranziția este realizată când asupra unui
proces ÎNTRERUPT se execută operația de suspendare .
 ÎNTRERUPT -SUSPENDAT –> ÎNTRERUPT : Asupra unui proces în trerupt și suspendat
se efectuează operația de reluare .
 BLOCAT –> BLOCAT -SUSPENDAT : Asupra unui proces ce așteaptă producerea unui
eveniment este executată operația de suspendare .
 ACTIV –> ÎNTRERUPT -SUSPENDAT : Tranziția are loc la suspendarea unui proc es
activ. Se execută operația de suspendare .
 BLOCAT -SUSPENDAT –> ÎNTRERUPT -SUSPENDAT : Pentru a avea loc această
tranziție trebuie îndeplinite următoarele condiții:
o procesul așteaptă producerea unui eveniment;
o procesul a fost suspendat;
o evenimentul aștept at se produce.
Tranziția are loc în urma operației de semnalizare .
 BLOCAT -SUSPENDAT –> BLOCAT : Tranziția se execută asupra unui proces ce aștepta
un eveniment, iar după aceea a fost suspendat. Se execută operația de reluare .

Prin operatia de distrugere, procesele trec din oricare din stările ACTIV , ÎNTRERUPT , BLOCAT ,
ÎNTRERUPT -SUSPENDAT și BLOCAT -SUSPENDAT în starea numită generic INEXISTENT .
În urma operației de creare, procesele trec direct în starea ÎNTRERUPT .

În general, dacă sistemul de operare păs trează o evidență a fiilor fiecărui proces este posibil să se
execute operații de tip distrugere, suspendare, reluare nu numai asupra unui proces specificat ci și
asupra unei descendențe a sa.

159
Exemplul 1 [Proces UNIX]. Executarea proceselor utilizator în sistemele de operare de tip UNIX se
face pe două niveluri: utilizator și nucleu, asociate nivelurilor de funcționare ale procesorului
sistemului de calcul. În mod utilizator, procesele își pot accesa numai propriile zone de instrucțiuni și
date, pe când în mod nucleu ele pot accesa și spațiul de adrese al nucleului. Nucleul sistemului de
operare este parte integrantă a oricărui proces în executare. Trecerea de la un nivel la altul se
realizează prin intermediul apelurilor sistem. După un apel sistem procesu l apelant va fi în mod
nucleu, iar la întoarcerea din apelul sistem, procesul revine în modul utilizator.

Relativ la stările proceselor, sistemele de operare de tip UNIX distinge următoarele stări de bază:
executare în mod utilizator, executare în mod nucleu, gata de executare ( întrerupt ), în așteptare
(blocat ) și zombie . În starea întrerupt , la un moment dat se pot afla mai multe procese. Ele sunt
gestionate de către nucleu prin intermediul unui fir de așteptare, fiind ordonate în funcție de prioritatea
acestora. Planificatorul alege procesul cu prioritatea maximă și îl lansează în executar e. În starea
blocat , un proces poate trece benevol când așteaptă terminarea unei operații de intrare/ieșire. De
asemenea, un proces este blocat în urma planificării unui proces prioritar. La un moment dat, un
proces blocat se poate afla pe disc (proces eva cuat) sau în memorie. Starea zombie caracterizează
toate procesele fiu în momentul terminării lor. Intrarea din tabela de procese este eliminată numai de
către procesul părinte. Prin urmare, procesele fiu nu sunt distruse imediat după terminarea executării
lor.

Informațiile referitoare la procese sunt memorate într -o tabela de procese, rezidentă în memorie. Cele
mai importante caracteristici ale procesului sunt: localizarea în memorie (adresa de memorie și adresa
de evacuare), dimensiunea, starea procesulu i, identificatorul procesului, identificatorul utilizatorului ce
a creat procesul, descriptorii fișierelor deschise de proces, catalogul curent, catalogul rădăcină asociat
procesului etc. Informațiile legate de fișiere, cataloage și utilizatori, fac parte din regiunea de date
asociată procesului ce va însoți procesul chiar și la evacuarea acestuia pe disc.

În sistemele de operare de tip UNIX procesele sunt create folosind apelul sistem fork. Procesul
apelant se numește proces părinte, iar procesul nou rezu ltat în urma apelului se numește proces fiu.
Inițial, imaginile celor două procese sunt identice, apoi ele pot avea propria lor imagine în memorie.
Astfel, dacă părintele își modifică o variabilă a sa, această modificare nu este vizibilă și pentru fiu, și
reciproc. Fișierele deschise înainte de apelul fork, sunt partajate între părinte și fiu. Prin urmare, dacă
un fișier a fost deschis de procesul părinte înainte de apelul fork, atunci el va fi deschis și pentru
procesul fiu.

Pe lîngă posibilitatea de crea re a unui proces nou, un proces poate lansa în executare un program
conținut într -un fișier executabil folosind unul din apelurile numite generic exec. Acestea determină,
în diverse variante, încărcarea unui program peste procesul curent și lansarea acestu ia în lucru. Prin
apelurile exec se pot lansa în executare programe care să folosească fișierele oferite de către procesul
părinte.

Operațiile cele mai importante, efectuate de către nucleu pentru realizarea unui apel fork sunt:
 alocă o intrare nouă în ta bela de procese;
 asociază un descriptor unic procesului fiu;
 realizează o copie logică a imaginii memoriei părintelui;
 întoarce în procesul părinte descriptorul fiului, iar în procesul fiu valoarea zero;
 trece procesul fiu în starea întrerupt (în memorie sau evacuat pe disc).

După crearea unui proces fiu, acesta poate executa același cod precum părintele său, sau un alt cod.
Acest lucru este posibil prin verificarea valorii returnate de apelul fork. Să presupunem că variabila

160
care reține valoarea returnat ă de apelul fork este cod_fiu. Atunci secvența (pseudocod) următoare
descrie situația în care cele două procese, după apelul fork, execută coduri diferite:

int cod_fiu;
cod_fiu = fork();
if (cod_fiu == 0) {codul_fiului();}
else {codul_p arintelui();}

Procesul fiu își poate afla propriul descriptor folosind apelul getpid . Un proces părinte care are mai
mulți fii va cunoaște descriptorii fiilor lui (creați prin apelul fork), iar la terminarea unuia dintre fii,
nucleul sistemului de operare îi va semnaliza trecerea în starea zombie a acestui fiu.

3.2. Sincronizarea proceselor

Problema generală a sincronizării constă în găsirea unui mecanism prin care un proces ACTIV să
devină BLOCAT sau să determine BLOCAREA altui proces, pentru a aștepta producerea unui
eveniment. Procesele pot interacționa în două feluri: indirect, prin concurarea pentru aceeași resursă și
direct , prin utilizarea simultană a aceleiași resurse și transmiterea unor informații. Dacă procesele sunt
independente din punct de vedere logic, ele interacționează indirect, controlul concurenței fiind
asigurat de nucleu. Când procesele concurează direct, controlul concurenței trebuie specificat la
implementarea acestora. Activitatea de coordonare a pro ceselor impune găsirea de soluții eficiente
privind: determinarea proceselor , interblocarea proceselor , excluderea mutuală a proceselor precum
și sincronizarea acestora.

Un sistem de activități este nedeterminat dacă rezultatele generate depind de viteza și ordinea de
executare a activităților. În caz contrar se spune că sistemul de activități este unic determinat .
Interblocarea proceselor apare atunci când acestea așteaptă indefinit pentru evenimente ce nu se vor
produce. Un astfel de eveniment poate fi, de exemplu, eliberarea unei resurse (fișier de date, zone de
memorie etc.). Interacțiunea proceselor conduce uneori la restricții în care două operații nu pot fi
niciodată executate în același timp. Cel mai important exemplu îl constituie utilizarea în com un, pentru
scriere/citire, a aceleiași resurse (fișier, zonă de memorie etc.). Resursa utilizată în comun este o
resursă critică . În această situație este necesară excluderea mutuală , adică un mecanism prin care,
dacă un proces utilizează resursa, celelalte procese sunt blocate. Partea unui program care solicită
resursa partajată se numește secțiune critică .

Soluția corectă a problemei excluderii mutuale trebuie să satisfacă următoa rele condiții:
a) exclusivitate : cel mult un proces să fie la un moment dat în secțiunea sa critică pentru aceeași
resursă;
b) evitarea blocajului reciproc : un proces nu trebuie să aștepte nelimitat pentru a intra în
secțiunea sa critică;
c) uniformitatea soluției : nu trebuie făcută nici o presupunere asupra vitezei de execuție a
proceselor;
d) evitarea dependențelor inutile : în afara secțiunii critice, un proces nu trebuie să suspende alte
procese.

Din punct de vedere hardware, dezactivarea întreruperilor la intrarea în secțiunea critică și activarea
acestora la părăsirea secțiunii critice constituie o soluție pentru rezolvarea problemei excluderii
mutuale. Această tehnică este folosită uneori de nucleul sistemului de operare, dar pentru procesele
utilizator folosire a acestei tehnici nu este recomandată. În domeniul soluțiilor software, au fost

161
propuse mai multe metode: utilizarea variabilei poartă, metoda alternării, metoda lui Peterson, metoda
semafoarelor etc.

3.2.1. Metoda variabilei poartă

Fie A și B două proce se ciclice. Metoda variabilei poartă presupune utilizarea unei variabile comune,
inițial permițând accesul oricărui proces în secțiunea sa critică. La intrarea în secțiunea critică procesul
va verifica dacă accesul este permis, iar în caz afirmativ va modi fica valoarea variabilei pentru a indica
intrarea sa în secțiunea critică. Mai precis, textul sursă al aplicației va fi:

int poarta;//variabilă comună cu valori binare:0 – deschis; 1 –
închis
poarta = 0; //inițial accesul este permis
//procesul A //proces ul B
do{}while ( poarta == 1);
poarta = 1;
cod_sectiune_critica_A();
poarta = 0; do{}while ( poarta == 1);
poarta = 1;
cod_sectiune_critica_B();
poarta = 0;

La o examinare atentă a codului se observă că este posibil ca, la un moment dat, ambele procese să
găsească variabila poarta cu valoarea deschis. Astfel, ambele procese vor fi simultan în secțiunea
critică. Deci, această soluție nu satisface condiția a).

3.2.2. Metoda alternării

Dacă procesele alternează în executare atunci se poate utiliza o variabilă comună care specifică
indicele procesului ce are dreptul să intre în secțiunea critică:

char are_dreptul ;//variabilă comună:'A' – proces 1; 'B' – proces 2
are_dreptul = 'A'; //inițial accesul este permis primului proces
//procesul A //procesul B
do{}while ( are_dreptul == 'B');
cod_sectiune_critica_A();
are_dreptul = 'B'; do{}while ( are_dreptul == 'A');
cod_sectiune_critica_B();
are_dreptul = 'A';

În aces t exemplu, ordinea de execuție este A, B, A, B, A, … etc. Dacă procesul A este lent atunci
procesul B va răspunde și el greu. Această soluție nu satisface condiția c). Ea presupune o ordine
predefinită a execuției celor două procese.

3.2.3. Metoda lui Peterson

O soluție software a problemei excluderii mutuale, în care procesele nu sunt strict alternate, a fost
propusă de Peterson în 1981. Idea metodei Peterson este prezentată prin următoarea secvența de cod.

int indicator; //0 – primul proces; 1 – al doilea proces
int cerere[2]; //tablou cu valori logice: 0 – fals; 1- adevarat
void intrare_in_sectiunea_critica (int proces){

162
int alt_proces;
switch (proces) {
case 0: alt_proces = 1; break;
case 1: alt_proces = 0;
}
cerere[proces] = 1;
indicator = proce s;
while ((indicator == proces) && (cerere[alt_proces])) do{};
}
void iesire_din_sectiunea_critica (int proces){
cerere[proces] = 0;
}

Înainte de intrarea în secțiunea critică fiecare proces apelează funcția intrare_in_sectiunea _critica cu
parametru indica tivul propriu. Procesul va intra în așteptare până când celălalt proces a apelat
procedura iesire_din_sectiunea_critica .

3.2.4. Metoda semafoarelor

Prin tehnica anterioară, dacă un proces încearcă să intre în secțiunea sa critică și nu o poate face, el va
consuma timpul procesorului pe perioada timpului alocat, sperând să intre în secțiunea sa critică. Acest
tip de așteptare se numește așteptare activ ă. Este de preferat ca un proces ce nu poate intra în
secțiunea sa critică să se autoblocheze în așteptarea eliberării resursei critice. Astfel, timpul
procesorului poate fi mult mai bine folosit. Un instrument util în tratarea acestei probleme este
semafo rul.

Un semafor boolean (sau binar) este o variabilă întreagă care poate lua numai valorile 0 sau 1. Asupra
unui semafor binar pot acționa următoarele operații: inițializare , operația P: determină decrementarea
cu 1 a semaforului (dacă nu este deja zero) și operația V: determină incrementarea cu 1 a semaforului
(dacă nu este deja 1).

Operațiile P și V sunt presupuse indivizibile, adică odată ce o astfel de operație a fost inițiată de un
proces, ea nu poate fi întreruptă de un alt proces înainte de a fi e xecutată complet.

Secțiunilor critice ale unui program li se poate asocia un semafor (inițial 1), iar un proces ce dorește
intrarea în secțiunea critică va executa operația P asupra acestui semafor. Această operație este
posibilă dacă valoarea semaforulu i nu este deja zero. În caz contrar, procesul se autoblochează. Deci,
un semafor are valoarea zero când un proces este în secțiunea sa critică, altfel va avea valoarea 1. La
ieșirea din secțiunea critică, procesul va executa operația V care determină creșt erea valorii
semaforului; dacă există un proces blocat atunci acesta este semnalizat (dacă sunt mai multe, este ales
unul la întâmplare) și este trecut în starea ÎNTRERUPT pentru a câștiga controlul procesorului.

Operațiile primitive P și V pot fi impleme ntate și în hardware, dar în mod normal sunt implementate în
software, folosind tehnica dezactivării întreruperilor. Gestionarea proceselor ce așteaptă trecerea de un
semafor se face cu ajutorul unei cozi de așteptare, operațiile de inserare în coadă și el iminare din coadă
fiind realizate de asemenea cu întreruperile dezactivate.

Cele de mai sus pot fi descrise prin următoarea secvență:

int excl;

163
excl =1; //
{cod_proces();}
….
P(excl);
{cod secțiune critică}
V(excl);
….

unde P(excl) și V(excl) sunt definite prin:

P(excl): if (excl == 1) excl=0;
else așteaptă_în_coada_asociată_semaforului(excl),
V(excl): if (coada_este_nevidă)
scoate_un_proces_din_coadă(excl);
else excl =1.

O generalizare a semafoarului binar o constituie semaforul numărător. Acest semafor este o variabilă
întreagă luând numai valori nenegative, iar operațiile asupra semaforului numărător sunt inițializarea,
operația PN și operația VN. Operațiile PN și VN sunt considerate indivizibile. Un semafor numărător
este iniți alizat cu o valoare x mai mare de 1, iar operațiile PN și VN acționează numai asupra unor
valori din domeniul 0..x. Dacă mai multe procese încearcă să execute operații PN sau VN asupra
aceluiași semafor, ordinea de executare este arbitrară. Dacă valoarea u nui semafor este zero, procesul
ce va încerca să execute o operație PN va fi suspendat și va aștepta ca un alt proces să execute o
operație VN. Această operație VN determină reluarea unui proces selectat arbitrar. Definițiile
operațiilor PN și VN asupra un ui semafor numărător N iau următoarea formă:
PN(N): if N>0 then N – else se suspendă procesul curent;
VN(N): if există un proces suspendat la acest semafor then selectează un proces pentru
execuție else N++;

Diferența esențială între un semafor numărător și un semafor boolean este aceea că mai multe procese
pot executa o operație P asupra unui proces numărător după care pot continua execuția. Efectul unui
semafor numărător poate fi obținut folosind două semafoare binare și o variabilă întreagă.

Exemplul 2 [Mecanisme de sincronizare în UNIX]. Sincronizarea proceselor în sisteme de tip UNIX
poate fi controlată de sistemul de operare sau de utilizator. În primul caz, mecanismul clasic de
sincronizare este conducta ( pipe). Sincronizarea controlată de utilizat or se realizează în principal prin
intermediul evenimentelor. Un eveniment reprezintă modalitatea de indicare a momentului în care un
proces, anterior BLOCAT , poate trece în starea ÎNTRERUPT (gata de rulare). Blocarea proceselor se
realizează folosind func ția sleep apelată cu un argument ce reprezintă evenimentul așteptat. La
producerea evenimentului de deblocare, nucleul sistemului de operare, folosind funcția wakeup , trece
toate procesele ce așteptau producerea evenimentului în starea ÎNTRERUPT . Din toat e aceste
procese numai unul singur va trece în starea ACTIV .

Un eveniment UNIX este un număr întreg, stabilit prin convenție, cunoscut de nucleul sistemului de
operare UNIX și asociat unei adrese. De exemplu, terminarea unui proces este un eveniment asoc iat
adresei părintelui procesului curent din tabela de procese. Producerea unor evenimente în sistem este
semnalată în sistemul UNIX, fie de către nucleu, prin funcția wakeup , fie prin intermediul semnalelor.
Semnalele UNIX sunt implementate cu ajutorul un or biți, plasați în tabela de procese. Aceștia pot fi
activați (li se atribuie valoarea 1) fie de către nucleu (la producerea unor evenimente privind resursele
sistemului de calcul), fie de către utilizator prin intermediul apelului kill. Verificarea primi rii unui
semnal este realizată de nucleu la trecerea procesului din starea în executare în mod nucleu la starea în

164
executare în mod utilizator, precum și la blocarea unui proces (atât înainte cât și după blocare). Un
proces poate memora semnale diferite, d ar nu poate păstra mai multe semnale de același tip. Cele mai
importante semnale recunoscute de sistemul de operare UNIX sunt: terminare proces; indicare excepții
(încercare de acces în afara limitelor permise); semnale sosite de la terminal, semnale sosit e de la
procese utilizator (prin funcțiile kill și alarm ); semnale pentru erori din directive etc.

La primirea unui semnal, acțiunea asociată este cea de terminare a procesului. Totuși există
posibilitatea de tratare, de către utilizator, a anumitor semna le. Pentru aceasta se poate folosi funcția
signal . Astfel, prin apelul signal (semnal, proc); se indică numărul unui semnal și rutina de tratare a
semnalului. Această rutină trebuie definită de utilizator.

Un alt tip de sincronizare o reprezintă sincroniza rea unui proces părinte cu fii săi. Pentru aceasta
sistemul de operare pune la dispoziție funcția wait ce permite blocarea unui proces până la terminarea
unuia dintre fii săi, sau până la primirea unui semnal. Dacă la apelul funcției wait unul dintre fii s e
terminase deja, blocarea nu se va mai realiza. Funcția primește ca parametru adresa unei variabile de
tip întreg în care procesul fiu va completa informații despre modul în care s -a terminat și întoarce
identificatorul procesului fiu ce s -a terminat. Un proces părinte cu descendenți ce nu apelează funcția
wait nu poate cunoaște modul de terminare și momentul terminării fiilor săi. Dacă se dorește
sincronizarea cu un anumit fiu, se repetă apelul wait (blocarea procesului) până la terminarea fiului
specific at. Indicarea fiului se realizează prin intermediul descriptorului de proces. O funcție importantă
utilizabilă de procese este funcția exit care provoacă terminarea procesului apelant. Prin aceasta sunt
închise toate fișierele deschise în proces și se tran smite controlul procesului părinte. Funcția folosește
un parametru de tip întreg, prin intermediul căruia se transmite procesului părinte modul de terminare
a procesului.

Alte funcții ce permit sincronizarea controlată de utilizator sunt: kill, alarm , pause și sleep . Funcția
kill trimite un semnal unui proces. Ea utilizează doi parametrii: identificatorul procesului și numărul
semnalului ce va fi transmis. Prin apelul funcției pause , un proces se autoblochează până la primirea
unui semnal. Această funcție nu are parametri. Un proces care utilizează funcția alarm cere sistemului
să transmită un semnal special (SIGALRM) după un interval de timp specificat prin parametrul
apelului. Func ția sleep este o combinație între pause și alarm și realizează suspendarea procesului ce
o apelează pentru un timp specificat prin argumentul de apel.

Unele din funcțiile prezentate sunt apeluri sistem, altele sunt funcții din biblioteca pentru dezvoltar e de
programe. Ultimele versiuni Unix/Linux conțin și un pachet de primitive ce implementează conceptele
de semafor, regiune de memorie partajată și coadă de mesaje.

3.3. Comunicare între procese

Mecanismele de sincronizare de mai sus asigură rezolvarea problemelor de interacțiune între procese,
prin introducerea unei secvențe logice de executare a acestora. Aceste mecanisme nu permit
transmiterea de informații între procesele ce cooperează la realizarea unui obiectiv comun.
Transmiterea unui semnal nu e ste suficientă; mai trebuie cunoscut și procesul care a transmis
semnalul. De asemenea, un proces ce transmite un mesaj poate fi interesat de confirmarea primirii și
utilizării mesajului.

Un prim mecanism de comunicare se bazează pe utilizarea zonelor com une de memorie. Cum o zonă
de memorie utilizată în comun este o resursă critică, se ajunge la programarea de secțiuni critice și
excludere mutuală. Datele comune pot fi interpretate ca mesaje transmise de un proces altui proces
care fie le așteaptă, fie le va găsi la nevoie.

165

Cel mai utilizat mecanism de comunicare prin intermediul unei zone comune cu sincronizare este
mecanismul producător -consumator . Fie procesele A și B ce comunică între ele prin depunerea și
respectiv extragerea de mesaje dintr -o zonă c omună numită tampon . Presupunem că zona de memorie
poate conține maxim N mesaje de aceeași lungime. De asemenea, presupunem că cele două procese au
o evoluție ciclică ce cuprinde operații de depunere și extragere de mesaje. Aceste operații pot fi
realizate dacă sunt îndeplinite următoarele condiții:
 la nivel de mesaj, operațiile se vor executa în excludere mutuală;
 nu se poate depune un mesaj atunci când nu mai există spațiu în tampon;
 nu se poate extrage un mesaj dacă zona tampon este vidă.

O propunere de rezolvare prin utilizarea metodei wakeup -sleep este:

#define N 100
int contor = 0;
void Produc(){
while (TRUE) produc(&mesaj);
if (contor == N) sleep();
inserare(mesaj);
contor++;
if (contor == 1) wakeup(Consumator);
}
Void Consumator(){
while(TRU E) if (contor == 0) sleep();
eliminare(&mesaj);
contor–;
if (contor == N – 1) wakeup(Produc);
utilizare(mesaj);
}

Analizați codul pentru a identifica punctele slabe ale acestei implementări.

3.4. Planificarea proceselor

Într-un sistem de calcul cu o singură unitate centrală, la un moment dat, procesorul poate executa un
singur proces. Dacă mai multe procese solicită serviciile procesorului, acestea sunt stocate în unul sau
mai multe fire de așteptare care sunt administrat e de nucleul sistemului de operare. Componenta
nucleului sistemului de operare dedicată planificării proceselor se numește planificator , iar strategia
utilizată se numește algoritm de planificare .

Pentru sistemele cu prelucrare pe loturi (sisteme batch), metoda de planificare a lucrărilor este foarte
simplă: după executarea completă a unei lucrări se continuă cu lucrarea următoare (situată, în general,
pe medii primare de stocare). Apariția sistemelor multiutilizator, interactive, în care procesorul este
utilizat simultan de mai multe procese (este tratat ca o resursă critică), a condus la creșterea
complexității algoritmilor de planificare. Planificatorul poate pune la dispoziție și un mecanism de
planificare, dar de cele mai multe ori el implementează o a numită strategie.

Se spune că un algoritm de planificare este ideal dacă satisface următoarele criterii:
 asigură accesul la procesor al tuturor proceselor (echitabilitate);
 asigură ocuparea permanentă a procesorului (eficiență);

166
 minimizează timpul de răs puns pentru utilizatorii în regim conversațional;
 minimizează timpul de executare a programelor organizate în loturi;
 maximizează numărul de procese executate pe unitatea de timp.

Analizând atent criteriile de mai sus, se observă că unele dintre acestea s unt contradictorii. De
exemplu, pentru a minimiza timpul de răspuns pentru utilizatorii în regim conversațional,
planificatorul nu trebuie să execute programele organizate în loturi. În această situație, utilizatorii care
au lansat în execuție loturi de pr ograme, nu vor fi mulțumiți de acest algoritm pentru că el nu satisface
criteriul al patrulea.

Dificultatea planificării constă și în faptul că orice program este un unicat. Structura programelor nu
poate fi anticipată. Unele procese pot solicita foarte multe operații de intrare -ieșire, pe când altele pot
utiliza procesorul pentru perioade lungi de timp. La activarea unui proces, planificatorul nu cunoaște
momentul când acesta se va bloca (pentru o operație de intrare -ieșire, așteptare la un semafor sau d in
alte motive). Pentru a se evita alocarea procesorului unui singur proces pentru o perioadă foarte lungă,
sistemele de calcul au în structura lor un ceas (orologiu) care determină apariția periodică a unei
întreruperi. La fiecare întrerupere a ceasului, sistemul de operare va stabili procesul care devine
ACTIV. Este posibil ca procesul curent să -și continue execuția sau să fie întrerupt. Strategia care
permite proceselor să fie întrerupte temporar se numește planificare prin reciclare .

În unele sisteme de calcul dedicate, precum sistemele de gestiune a bazelor de date mari, un proces
părinte poate crea mai multe procese fiu ce execută diferite operații, acestea fiind complet sub
controlul procesului părinte. Pentru astfel de sisteme, un algoritm de planificare stabilit la
implementarea sistemului de operare nu poate să ofere rezultate foarte bune. Situația se îmbunătățește
dacă sistemul de operare pune la dispoziția programatorilor sau operatorilor un mecanism de
planificare pentru a fi f olosit la realizarea unei strategii proprii de planificare. Acest obiectiv este
realizat prin parametrizarea algoritmului de planificare. Parametrii planificatorului pot avea valori
implicite sau pot primi valori prin intermediul unor comenzi ale operatoru lui sau apeluri sistem din
programele ce intervin în planificarea proceselor.

Strategiile de alocare sunt de mai multe tipuri: a) strategii pe bază de prioritate; b) strategii pe bază de
termen; c) strategii cu un pas; d) strategii cu mai mulți pași.

Prioritatea este un număr asociat fiecărui proces. Valoarea priorității este, în general, invers
proporțională cu numărul ce o reprezintă. Prioritatea poate fi indicată în mod explicit de utilizator
printr -o comandă de control (de exemplu, nice în UNIX/Linu x etc.) sau evaluată de sistem pe baza
unor indicații date de utilizator sau a unor informații de control obținute de sistemul de operare (de
exemplu, modelul priorității dinamice din UNIX/Linux).

Planificarea proceselor poate fi realizată folosind unul s au mai multe fire de așteptare. În sistemele cu
fir de așteptare unic, la acceptarea unui nou proces în sistem, planificatorul îl introduce în firul de
așteptare în locul ce corespunde priorității sale. În sistemele ce utilizează mai multe fire de așteptar e,
pentru fiecare prioritate se gestionează un fir de așteptare. Procesele dintr -un fir sunt luate în
considerare numai după ce toate firele de așteptare asociate priorităților mai mari sunt vide.

Aceste strategii pot fi implementate atât în sistemele cu timp partajat cât și în sistemele în care
procesorul este alocat continuu unui proces pe toată durata sa de viață. În cazul utilizării în regim
partajat a procesorului, procesul curent poate fi întrerupt în favoarea unui nou proces. Dacă prioritatea
noului proces este mai mare decât prioritatea procesului curent atunci procesorul este retras procesului
curent, care revine în firul de așteptare. Astfel, procesorul este alocat noului proces.

167
Uneori, o strategie bazată pe conceptul de prioritate poate fi nesa tisfăcătoare. De exemplu, unele
procese cu prioritate mică pot fi întârziate foarte mult atunci când în sistem se află un număr
considerabil de procese cu prioritate mare. De asemenea, în anumite aplicații este strict necesară
încheierea execuției unor pro cese într -un interval de timp precizat.

Dacă se cunosc durata de execuatre a fiecărui proces și diverse informații despre încărcarea sistemului
(totalitatea proceselor aflate în sistem la un moment dat), atunci procesului i se poate asocia o
prioritate variabilă ce crește odată cu apropierea termenului limită de finalizare. Fiind dificil de
apreciat durata de executare a proceselor, strategiile bazate pe termen sunt mai puțin utilizate.

Strategiile de planificare cu un pas sunt acele strategii prin care alocarea procesorului este continuă,
adică procesorul nu este retras unui proces decât după terminarea executării acestuia.

În funcție de organizarea firului de așteptare distingem: strategia " primul sosit, primul servit " (FIFO),
strategia " ultimul sosit , primul servit " (LIFO) și strategia " cel mai scurt timp de executare ".

Strategia de alocare FIFO satisface cererile de planificare a proceselor în ordinea sosirii acestora. Este
utilizat un singur fir de așteptare (o coadă ). Această strategie dezavantaje ază procesele scurte când
acestea sunt precedate de procese lungi.

Strategia de alocare LIFO administrează un fir de așteptare de tip stivă , astfel încât ultimul proces
intrat în sistem este lansat în executare imediat ce procesorul devine disponibil. Dac ă procesele sunt
create mai des decât sunt executate, primele procese sosite în sistem pot să aștepte foarte mult pentru
obținerea controlului procesorului.

Strategia " cel mai scurt timp de executare " utilizează durata de executare a proceselor. Această
strategie acordă o prioritate mai mare proceselor scurte. Din acest motiv, în firul de așteptare procesele
sunt aranjate în ordinea crescătoare a timpilor de executare. Prin această strategie sunt favorizate
procesele scurte în detrimentul celor lungi. Pent ru implementarea unui astfel de algoritm este necesară
cunoașterea duratei de executare a fiecărui proces.

Strategiile în mai mulți pași se bazează pe întreruperea procesului curent după un interval de timp,
numit cuantă , reintroducerea acestuia în firul de așteptare și alocarea procesorului unui alt proces.
Acest mod de alocare a procesorului este numit alocare prin reciclare . Dacă alocarea prin reciclare
folosește un fir de așteptare ce conține procesele în starea ÎNTRERUPT, gestionat conform disciplinei
FIFO se obține algoritmul " round -robin ". Planificatorul extrage din coadă procesul ce va primi
controlul procesorului pe durata unei cuante de timp q. Dacă procesul nu se termină în cursul alocării
curente, atunci el revine în coada de așteptare. Prin ace astă metodă momentul alocării procesorului nu
mai depinde de timpii de executare ai proceselor create anterior. Parametrul q poate lua valori de
ordinul milisecundelor și este stabilit în funcție de tipul și obiectivele sistemului de operare. Trecerea
de la un proces la altul necesită un anumit timp pentru salvarea/încărcarea registrelor, actualizarea
unor tabele și liste de evidență etc. Să presupunem că această operație necesită 4 milisecunde, iar q =
16 milisecunde. Atunci din 100 de milisecunde, 80 de m ilisecunde sunt utilizate pentru execuție
efectivă, iar 20 de milisecunde sunt utilizate pentru trecerea de la un proces la altul. Dacă q = 500,
atunci timpul efectiv de execuție se mărește substanțial. În concluzie, dacă cuanta q are o valoare mică,
vor a vea loc prea multe treceri de la un proces la altul, micșorând eficiența procesorului, iar dacă
cuanta q are o valoare prea mare, procesele utilizatorilor în regim conversațional vor fi tratate
nesatisfăcător. Într -un sistem pentru conducerea proceselor in dustriale, q poate fi ales astfel încât toate
interacțiunile proces industrial – sistem de calcul să se încheie într -o singură cuantă. Pentru sisteme
multiutilizator, un compromis rezonabil constă în alegerea unei cuante de 100 de milisecunde.

168
Alocarea pr in reciclare bazată pe mai multe fire de așteptare este o extensie a strategiei "round -robin",
prin definirea mai multor valori pentru cuanta de timp, q 1 < q 2 < … < q n. Procesele în starea
ÎNTRERUPT sunt repartizate în n fire de așteptare FIFO: Q 1, Q 2, …, Q n, firul Q i fiind asociat cuantei
qi. Procesele care tocmai au trecut în starea ÎNTRERUPT sunt depuse mai întâi în firul Q 1.
Planificatorul alocă procesorul unui proces din firul Q i numai dacă toate firele Q j (j < i) sunt vide.
Procesul din firul Q i care a primit controlul procesorului, păstrează acest control cel mult o cuantă q i.
Dacă procesul se blochează sau este suspendat înainte de expirarea cuantei q i, el este trecut în “coada”
proceselor din starea BLOCAT sau BLOCAT -SUSPENDAT. Altfel, după ex pirarea cuantei q i,
procesul este trecut în firul Q i+1 și este selectat un nou proces pentru a primi controlul procesorului.
Procesele din firul Q n sunt trecute tot în firul Q n după expirarea cuantei q n. Se observă că această
strategie favorizează lucrăril e scurte, prin întârzierea celor de durată mai lungă. Strategia de alocare
prin reciclare ce utilizează mai multe fire de așteptare împreună cu strategia bazată pe priorități pot
conduce la algoritmi de planificare adecvați sistemelor interactive. O astfel de strategie este
implementată în cadrul sistemului de operare UNIX/Linux.

Sunt posibile și alte strategii precum: strategia " cel mai puțin executat ", strategia " cel care se va
termina primul " și strategia " procesul cu cel mai scurt termen final ".

Strategia " cel mai puțin executat " depinde de timpul necesar de executare, dar spre deosebire de
strategia " cel mai scurt timp de executare ", aceasta acționează dinamic urmărind timpul care a mai
rămas până la terminarea executării. Algoritmul echilibrează accesul proceselor la resursele de calcul
și este utilizat, mai ales, în sistemele care nu pot obține informații despre procese.

Strategia " cel care se va termina primul " conduce la un algoritm orientat pe timpul de executare care
selectează pentru plani ficare procesul cu cel mai scurt timp rămas pentru executare.

Aplicarea algoritmului presupune atât cunoașterea apriori a duratei aproximative de execuție cât și
urmărirea timpului consumat în executare pentru fiecare proces. Algoritmul favorizează ieșire a din
sistem a proceselor care se apropie de final descongestionând încărcarea sistemului și contribuind la
asigurarea unor timpi de răspuns acceptabili. Totuși, procesele cu durată mare de executare sunt
dezavantajate.

Strategia " procesul cu cel mai scur t termen final " presupune cunoașterea a trei elemente: momentul
sosirii, durata executării și momentul până la care executarea procesului trebuie să fie terminată. Pe
baza acestor elemente se stabilește ordinea de executare în funcție de rezerva de timp pe care o are
fiecare proces față de termenul final. Acest algoritm este specific sistemelor de prelucrare în timp real,
la care răspunsul trebuie dat în cadrul unui interval de timp prestabilit (de exemplu, sistemele de
operare QNX ( http://www.qnx.com/content/qnx/en/products/neutrino -rtos/index.html )

Strategiile prezentate până aici au presupus, mai mult sau mai puțin, că toate procesele în starea
ÎNTRERUPT sunt încărcate în memoria centrală. Dacă nu există memorie internă suficientă, este
posibil ca unele din procesele în starea ÎNTRERUPT să fie păstrate pe disc. Această situație conduce
la modificarea algoritmului de planificare. Cea mai bună soluție constă în separarea algo ritmului de
planificare pe două niveluri. Nivelul 1 al planificării va răspunde de evacuarea pe disc a unor procese
ce au rămas în memorie o anumită perioadă de timp și încărcarea în memorie a proceselor care au stat
pe disc o perioadă mai lungă de timp. P lanificarea propriu -zisă este realizată de nivelul 2 al planificării
pe baza uneia din strategiile de mai sus.

În sistemele de operare UNIX, procesorul este alocat de către nucleu fiecărui proces în cuante de timp
egale, de valoare fixată. Aceasta implică faptul că la un moment dat, un singur proces aflându -se în
starea întrerupt obține controlul procesorului. După un interval de timp cel mult egal cu durata cuantei

169
de timp alocate, nucleul va trece procesul curent din starea în executare în starea gata de executare
(întrerupt) și va aduce în starea în executare un alt proces. Executarea procesului întrerupt va fi reluată
mai târziu. Pentru a gestiona procesele, nucleul asociază fiecărui proces activ câte o prioritate,
calculată dinamic și asociată direct c u timpul de executare consumat de procesul respectiv.
Componenta nucleului care selectează procesul ce va fi plasat în starea în executare se numește
dispecer. Acesta va selecta procesul cu prioritatea cea mai mare dintre cele care se află în starea gata
de executare și care sunt încărcate în memorie. Pentru a se putea realiza planificarea pe bază de
prioritate a proceselor, fiecare intrare în tabela de procese conține un câmp care specifică prioritatea
procesului. În funcție de modul de executare (utilizat or sau nucleu), prioritatea unui proces este
prioritate utilizator sau prioritate nucleu. Fiecare clasă de priorități conține un număr finit de valori ale
acestora, iar fiecare prioritate are asociată o coadă de procese.

Nucleul calculează prioritatea un ui proces (în starea specifică acestuia) pe baza următoarelor reguli
(nivelul 2 al planificării):
 atribuie o prioritate unui proces înainte ca acesta să fie trecut în starea blocat (în așteptare),
corelând valoarea atribuită cu cauza care a produs trecerea procesului în așteptare;
 recalculează prioritatea procesului la revenirea acestuia în starea în executare în mod
utilizator;
 rutina de tratare a întreruperilor de ceas ajustează prioritățile proceselor din secundă în
secundă și determină o recalculare de către nucleu a acestora (Se evită astfel controlarea
îndelungată a procesorului de către un singur proces.);
 prioritatea unui proces aflat în executare în mod utilizator este funcție de timpul recent de
folosire a procesorului.

Timpul de lucru al procesor ului este controlat prin intermediul driver -ului de ceas, care îndeplinește
următoarele funcții:
 reprogramează ceasul de timp real al sistemului de calcul;
 programează executarea unor funcții interne ale nucleului în funcție de valorile unor contoare
de timp interne;
 măsoară timpul sistem;
 transmite semnalul SIGALRM către procese (ce apelează funcția alarm );
 activează periodic procesul swapper răspunzător de transferarea unor procese între memorie și
disc (nivelul 1 al planificării) ;
 controlează planificarea proceselor în execuție.

Dacă sistemul de calcul este multiprocesor/multinucleu, selectarea procesului din coada de procese
aflate în starea ÎNTRERUPT se face după algoritmii de mai sus. Afectarea procesorului este însă o
problemă complexă. În continuare se presupune că toate procesoarele sistemului de calcul au aceleași
caracteristici și că un astfel de procesor poate fi liber (nu execută nici o instrucțiune) sau ocupat. Un
procesor ocupat poate aștepta rezolvarea unui conflict hardware (secve nța de declanșare a unei
întreruperi) sau poate fi activ (execută instrucțiuni obișnuite). Un procesor este considerat liber numai
la inițializarea sistemului de operare. Dacă la un moment dat nu există nici un proces capabil să devină
ACTIV pe un anumit p rocesor, se lansează în executare un proces fals (o instrucțiune de așteptare).
Când planificatorul sistemului de operare al sistemului multiprocesor primește controlul, în sistem
sunt prezente n 1 procese în starea ÎNTRERUPT, n 2 procese în starea ACTIV și n3 procese false.
Repartizarea procesoarelor sistemului poate fi descrisă prin: m 1 procesoare ocupate cu procesele
active, m 2 procesoare ocupate cu procese false și un procesor ocupat cu planificatorul. Evident n 2 =
m1, n3 = m 2 și m 1+m 2+1 reprezintă număr ul total de procesoare din structura sistemului de calcul.

170
Pentru a lansa noi procese în execuție, planificatorul trebuie să oprească unele procesoare (pe care se
execută fie lucrări mai puțin prioritare, fie procese false). Unele procesoare nu pot fi op rite deoarece
pot avea destinație specială (stabilită chiar de planificator) sau așteaptă rezolvarea unor conflicte
hardware.

4. Gestiunea mem oriei interne. Memoria virtuală
4.1. Generalități

Memoria este unul dintre cele mai importante subsisteme ale unui sistem de calcul. Ea are rolul de a
înmagazina informația. Cum memoria unui sistem de calcul este o resursă limitată, se impune
gestiunea eficientă a acesteia. Se știe (secțiunea 1) că un sistem de calcul are în componența sa
memorie internă și memori e externă. Pentru obținerea unui răspuns rapid la o cerere de citire/scriere,
memoria internă este structurată în memorie "cache" și memorie operativă. Memoria "cache" are o
capacitate mică, dar un timp de acces foarte scurt. Ea conține informațiile cele m ai recent utilizate de
către unitatea centrală. La orice acces, procesorul verifică dacă data solicitată se află în memoria
"cache" și numai după aceea solicită memoria operativă. În caz afirmativ, schimbul de informații are
loc imediat, altfel data solici tată este căutată în memoria operativă. Memoria "cache" este prezentă la o
mare varietate de sisteme de calcul. Memoria operativă conține programele și datele pentru toate
procesele existente în sistem. Memoria operativă are o capacitate mare (mai mulți GB ) și timpul de
acces este foarte scurt. Memoria externă este structurată în memorie secundară și memorie de arhivare.
Memoria secundară extinde memoria operativă și apare în sistemele de operare cu memorie virtuală.
Suportul fizic al memorie secundare este , în general, discul magnetic. Memoria de arhivare constă din
fișiere (sau baze de date) rezidente pe diferite suporturi magnetice (discuri, casete speciale etc.).

Componenta sistemului de operare care se ocupă cu gestiunea memoriei se numește manager de
memorie. Această componentă nu trebuie confundată cu o extensie a sistemului de calcul ("cuplorul"
de memorie) care asigură gestiunea unei cantități mai mari de memorie față de posibilitatea de
adresare a procesorului. Adresa reprezintă acel atribut al mem oriei interne sau externe pe baza căruia
locațiile (celulele) de memorie pot fi localizate direct, repetitiv și univoc, în vederea prelucrării
informației memorate sau stocării unei informații noi.

Procesul de localizare a unei zone din memoria internă s au externă se numește adresare. Sunt posibile
mai multe tipuri de adresare: absolută, relativă, indexată, indirectă, asociativă etc., în funcție de tipul
adresei considerate. Prin adresare absolută, referirea zonelor de memorie se face printr -o adresă
(num ită absolută) stabilită în raport cu o poziție inițială numită origine. Adresarea relativă înseamnă
specificarea unei adrese ce va fi translatată pentru a se obține o adresa absolută. Procesul de translatare
folosește o adresă convențională numită bază de adresare . Adresarea indexată facilitează prelucrarea
datelor stocate succesiv, în care poziția unui element se determină pe baza unor indici. Adresa efectivă
se obține folosind adresa de început a zonei și indexul elementului (format pe baza indicilor). De
obicei, indexul este memorat într -un registru general sau în registre speciale numite registre index.
Prin adresare indirectă este posibil să se utilizeze o locație de memorie al cărei conținut reprezintă
adresa unei alte locații. Adresarea indirectă poat e fi realizată în cascadă, pe mai multe nivele. În cazul
adresării asociative, localizarea unei zone de memorie se realizează pe baza conținutului acesteia.

În sistemele de operare cu multiprogramare este necesară asigurarea protecției memoriei. Astfel,
fiecare unitate de memorie alocată (partiție, pagină etc.) conține o informație numită cheie de
protecție , iar fiecare entitate încărcabilă (parte a unui program, context al unui proces) dispune de o
cheie de acces . Acestor chei li se mai asociază câteva co duri speciale (acces în citire, acces în scriere

171
și acces în executare). În principiu, protecția memoriei este asigurată prin parcurgerea următoarelor
etape:
 La orice invocare a unei zone de memorie se compară cheia de protecție cu cheia de acces;
dacă apa re nepotrivire atunci accesul este interzis și se emite un semnal adecvat;
 Dacă cheile coincid, atunci se compară codurile asociate cheilor; accesul este permis numai în
cazul potrivirii complete.

Mecanismul de protecție este puternic dependent de sistemul de calcul. Aproape fiecare sistem de
calcul are propriul mecanism de protecție.

În primele sisteme de calcul memoria era alocată în întregime unui singur program utilizator. Toate
informațiile necesare, aflate pe un suport extern, erau încărcate în memoria principală prin operații de
intrare/ieșire explicite. Evoluția aplicațiilor spre programe de dimensiuni mari, în condițiile unei
memorii limitate, a obligat pe realizatorii de sisteme de operare să acorde o importanță majoră
gestiunii memoriei p rincipale. Soluțiile oferite au condus la conceptele de partiție, pagină, migrație a
proceselor între diferite niveluri ale memoriei (de exemplu, între memoria operativă și disc) precum și
la conceptul de memorie virtuală.

Principalele funcții ale manage rului de memorie sunt: evaluarea funcției de translatare a adresei
(relocare), asigurarea protecției memoriei, organizarea și alocarea memoriei operative, gestiunea
memoriei secundare și asigurarea migrației informației între memoria principală și memoria auxiliară.

4.2. Structuri de date și algoritmi

Înainte de a prezenta diverse strategii de alocare a memoriei operative, se vor descrie cele mai
importante sisteme de evidență și alocare/eliberare a memoriei. Prin definiție, o zonă este un ansamblu
de locații succesive de memorie operativă. Implementarea unui algoritm de gestiune a memoriei
necesită rezolvarea următoarelor probleme: reprezentarea spațiului liber, stabilirea criteriilor pentru
alegerea unei zone libere și definirea unei strategii de elibe rare a zonelor ocupate și luarea unei decizii
când nici o zonă liberă nu convine.

O zonă este identificată prin dimensiunea sa și prin adresa primei sale locații. Aceste informații
formează descriptorul zonei. În cazul în care numărul zonelor libere varia ză, descriptorii lor pot fi
plasați în zonele libere pe care le definesc și apoi sunt înlănțuiți între ei. Înlănțuirea poate fi simplă sau
dublă. În cazul înlănțuirii simple primele cuvinte ale fiecărei zone libere conțin dimensiunea acesteia
(dim) și adre sa următoarei zone libere ( leg).

Pentru asigurarea unei gestiuni complete, mai este necesar un indicator al primei zone libere ( init) și
de o marcă de sfârșit de lanț, care poate fi explicită ( fin), sau adresa primei zone libere (înlănțuire
simplă circul ară). Dacă {adr[i]: 0 ≤ i ≤ n} este mulțimea adreselor zonelor libere, atunci
este verificată relația: adr[i]->leg == adr[i+1].

Înlanțuirea dublă este realizată în felul următor. În fiecare zonă liberă se plasează informațiile legd,
legs și dim, care au respectiv semnificațiile: adresa următoarei zone libere (înlănțuire înainte), adresa
zonei libere anterioare (înlănțuire înapoi) și dimensiunea. Dacă adr[i] este a i -a zonă din lanț,
înlănțuirea dublă este caracterizată de relația:
adr[i]->.legd->.legs = = adr[i] == adr[i] ->.legs->.legd.
În fiecare zonă se poate memora și un indicator de ocupare ( ind), care specifică starea zonei
respective: ind == 0 pentru zonă liberă, ind == 1 în cazul în care zona este ocupată. Accesul la

172
zonele libere este definit de d oi indicatori, unul pentru începutul și altul pentru sfârșitul lanțului
acestor zone.

Această reprezentare a spațiului liber reduce timpul de căutare a unei zone libere și permite
recuperarea zonelor eliberate. De asemenea, informațiile de gestiune pot f i memorate și în zonele
alocate, cu condiția ca acestea să fie protejate. Ele pot fi folosite de algoritmul de înlocuire, pentru
alegerea zonei care va fi reacoperită, atunci când nu mai există zone libere sau c|nd cele care există nu
convin.

Ordinea zone lor libere influențează eficiența algoritmilor de gestiune. Sunt posibile mai multe moduri
de ordonare: 1) în ordine cronologică a eliberărilor; 2) în ordine crescătoare sau descrescătoare a
adresei de început; 3) în ordine crescătoare sau descrescătoare a dimensiunii zonelor; 4) clasare în liste
diferite, în funcție de alte criterii.

Alegerea unei metode de ordonare depinde de algoritmii utilizați pentru satisfacerea cererilor de
alocare și de eliberare. În urma unei cereri de alocare, trebuie aleasă o zo nă liberă potrivită. Uneori o
cerere poate fi satisfăcută folosind o zonă liberă mai mare, iar diferența (reziduul), dacă nu este prea
mică, se introduce în lanțul zonelor libere. În continuare se vor prezenta cele mai importante metode
de selecție a unei zone libere pentru satisfacerea unei cereri de alocare [1]:
1. Algoritmul primei alegeri : Această metodă alege prima zonă posibilă; dacă se solicită o zonă
de n locații, atunci se caută în lanțul zonelor libere până când se găsește o zonă de dimensiune
l, astfel încât l ≥ n.
2. Algoritmul celei mai bune alegeri : Prin această metodă se alege cea mai mică zonă liberă
posibilă, adică aceea care conduce la cel mai mic reziduu.

Pentru implementarea celor două metode sunt posibile mai multe variante:
 Printre zonele care satisfac criteriul de alegere, se întârzie alocarea acelor zone care
conduc la un reziduu prea mic, în raport cu dimensiunea medie a zonelor. În acest
mod se asigură refolosirea zonelor rămase .
 Se respinge alocarea zonelor disponibile, ale căror zone vecine vor fi eliberate foarte
curând. Aceste zone vor fi alocate numai în cazul în car e nu există altă posibilitate.
Prin această strategie se permite crearea unor zone libere de dimensiune mare .
 Dacă alegerea unei zone disponibile produce un reziduu mai mic decât o valoare
prestabilită, atunci se va aloca întreaga zonă. Astfel se evită cr earea unor goluri de
dimensiune mică , care nu vor putea satisface nici o cerere de alocare.

3. Algoritmul înjumătățirii (metoda camarazilor): Această metodă gestionează zone de memorie
cu dimensiunea egală cu o putere a lui 2. Presupunem că memoria operativă are capacitatea
totală 2m. Dacă dimensiunea unei cereri de alocare nu este o putere a lui 2, atunci se alocă o
zonă a cărei dimensiune este o putere a lui 2, imediat superioară dimensiunii dorite. Ideea
acestei metode este de a menține liste separate pent ru fiecare dimensiune 2k (0 ≤ k ≤ m)
pentru care există cel puțin o zonă liberă. Inițial există o singură zonă liberă de dimensiune 2m.
Permanent, dacă este solicitată o zonă de dimensiune 2k și nu există nici o zonă liberă de
această dimensiune, o zonă liberă mai mare este împărțită în două părți egale; după un număr
finit de astfel de operații se obține o zonă cu dimensiunea dorită și alocarea poate fi
satisfăcută. Când două zone vecine, de dimensiune 2k, devin libere, ele sunt imediat regrupate
pentru a obține o singură zonă de dimensiune 2k+1. În acest scop alocatorul memoriei folosește
un bit de ocupare pentru a arăta starea fiecărei zone. Memoria neocupată este reprezentată prin
liste asociate fiecărei dimensiuni. De exemplu se pot utiliza liste dubl u înlănțuite. Câmpurile
legd și legs, sunt plasate chiar în zonele libere. Elementele fiecărei liste sunt accesibile prin
specificarea a doi indicatori care se referă la primul și respectiv la ultimul element al lanțului.

173

4. Metoda numerelor lui Fibonacci : Această tehnică este asemănătoare metodei camarazilor, dar
în loc de a diviza o zonă liberă de o anumită dimensiune în două părți egale, ea decupează o
zonă de dimensiune ai în două zone de dimensiuni a i-1 și a i-2, unde a i, ai-1, ai-2 sunt numerele lui
Fibonacci: a i = a i-1 + a i-2, a1 = a 2 = 1. La terminarea executării unui proces este necesară
eliberarea zonelor de memorie care i -au fost alocate. Pot fi întâlnite următoarele situații:
 Zona eliberată se află între două zone libere, prin urmare, este preferab il ca cele trei zone
libere să fie regrupate, pentru a se obține o singură zonă liberă de dimensiune egală cu
suma dimensiunilor celor trei zone unite;
 Zona eliberată se află între o zonă liberă și una ocupată (caz particular al primei situații);
 Zona elib erată se află între două zone ocupate. În acest caz, zona respectivă este adăugată
listei zonelor disponibile.

4.3. Metode elementare de gestiune a memoriei operative

În acest paragraf se vor prezenta cele mai cunoscute metode de gestiune a memoriei operative, care nu
asigură utilizarea automată a memoriei secundare. Schimbul de informații între memoria operativă și
memoria secundară este realizat de programele utilizatorilor prin operații de intrare -ieșire explicite.

1. Alocarea integrală și permanentă : Această metodă presupune alocarea integrală și în mod
permanent (a memoriei) pentru un singur program utilizator. În cazul existenței memoriei
secundare, utilizatorul trebuie să -și gestioneze singur, în mod explicit, transferurile de
informații între mem oria principală și memoria secundară. Metoda prezentată are următoarele
caracteristici:
 simplitate – programatorul are acces direct la mecanismul de adresare al calculatorului și
nu necesită o interfață complexă pentru comunicația utilizator < –> sistem;
 cost minim – atât pentru implementarea metodei, cât și pentru utilizarea curentă a
algoritmului de alocare (în mod evident, timpul consumat pentru gestiunea memoriei este
nul).
Această metodă, deși simplă și ușor de implementat, este foarte limitată în f acilități: nu
permite înlănțuirea secvențială a mai multor lucrări, iar resursele sistemului (timpul unității
centrale, memoria principală etc.) nu pot fi partajate între mai mulți utilizatori.

2. Utilizator unic și rezident : Această metodă rezolvă problema înlănțuirii automate a lucrărilor.
Pentru aceasta, nucleul sistemului de operare are o componentă cunoscută sub numele de
supervizor (monitor). Supervizorul este rezident în memoria centrală (într -o zonă rezervată,
numită rezidență) pe parcursul execuției tuturor programelor utilizatorilor, care pot utiliza cea
mai mare parte a memoriei principale. Pentru implementarea acestei metode este necesar ca
nivelul hardware să dispună de un mecanism de protecție a rezidenței. În general se utilizează
un registru li mită. Dacă instrucțiunea în curs de executare se află în spațiul rezervat
supervizorului, ea poate să modifice conținutul oricărei locații din memoria centrală. Dacă,
totuși, această instrucțiune se execută în spațiul utilizator, ea poate accesa numai memo ria care
nu este rezervată rezidenței. Singura modalitate de acces la zona rezidenței este utilizarea
apelurilor supervizorului.

3. Utilizarea partițiilor fixe : Această metodă este caracteristică sistemelor cu multiprogramare.
Memoria operativă este împărțit ă într -un număr de zone de dimensiune fixă (numite partiții) în
care se încarcă câte un program utilizator. Administratorul sistemului de calcul (operatorul)
poate realiza această partiționare, manual, la fiecare pornire a sistemului.

174

4. Utilizarea partițiil or variabile : Această metodă extinde tehnica partițiilor fixe și permite o
exploatare economică a memoriei centrale. Spațiul memoriei operative este divizat într -un
număr arbitrar de zone de dimensiuni variabile (numite partiții variabile), ce corespund
dimensiunii programelor care urmează a fi executate în aceste partiții. În funcție de încărcarea
sistemului, nucleul sistemului de operare actualizează numărul și dimensiunile partițiilor. La
încheierea execuției unui program, acesta părăsește memoria centra lă, iar celelalte programe
sunt deplasate spre una din limitele memoriei operative pentru a obține la cealaltă extremitate,
o singură zonă liberă (compactarea memoriei). Această zonă va fi alocată, în continuare, altor
programe care vor fi lansate în exec uție. De asemenea, hardware -ul sistemului de calcul
trebuie să utilizeze, în primul rând, un mecanism de protecție a partițiilor (de exemplu registre
limită) și, în al doilea rând, un mecanism de relocare dinamică a programelor (similar utilizării
partiții lor fixe).

4.4. Metode avansate de gestiune a memoriei operative

În această secțiune vor fi prezentate metode de gestiune a memoriei operative care se bazează pe
utilizarea (de către nucleul sistemului de operare) a memoriilor secundare pentru creșterea eficienței
exploatării resurselor și îmbunătățirea timpului de răspuns al sistemului. Metodele prezentate sunt de
două categorii. Din prima categorie fac parte acele metode prin care se alocă o zonă continuă de
memorie. O alternativă la acestea o reprezin tă metodele care împart memoria în segmente, pagini sau,
și segmente și pagini, fiecărui program fiindu -i alocate segmente sau pagini, nu neapărat vecine.

4.4.1. Alocare continuă

Metodele de gestiune a memoriei operative aparținând primei categorii se a plică în următoarele
condiții: 1) un program poate fi executat numai dacă este situat în întregime în memoria centrală; 2)
celelalte programe, cu care partajează resursele sistemului, nu sunt integral și în mod permanent
rezidente în memoria principală.

1. Metoda transferului minim : Această metodă de gestiune a memoriei este înrudită cu metoda
partițiilor variabile, dar ea nu impune programelor ocuparea de zone disjuncte în memoria
centrală. Procesul ACTIV , este încărcat începând cu prima adresă din zona ope rativă. Orice
proces, care ocupă locații din memoria operativă susceptibile de a fi reacoperite de procesul ce
urmează a fi activat, este salvat în memoria externă, de unde va fi reîncărcat în momentul în
care va fi reactivat (la trecerea din starea ÎNTRER UPT în starea ACTIV ). Acest algoritm
este conceput astfel încât cantitatea informațiilor transferate din (sau spre) memoria operativă
să fie minimă. Într -adevăr, dacă procesul ACTIV are dimensiuni mai mari decât ale celui care
urmează a fi activat, atunci se salvează numai partea reacoperită de noul proces, restul
procesului rămânând în continuare în memoria operativă. Dacă sistemul de operare este
încărcat la baza memoriei RAM, atunci pentru implementarea acestui algoritm, hardware -ul va
utiliza numai un s ingur registru de protecție a limitei inferioare a procesului ACTIV .

2. Metoda suprapunerii minime : Această metodă este o variantă îmbunătățită a metodei
transferului minim. Restricția impusă procesului curent, de a fi încărcat de la prima adresă a
memoriei centrale disponibile, este eliminată. Astfel, se permite încărcarea proceselor în orice
zonă continuă din memoria operativă. Această zonă este aleasă astfel încât reacoperirea să fie
minimizată (eventual eliminată). Algoritmul poate fi implementat folosind următoarele idei :

175
 memoria centrală este împărțită în zone de dimensiune fixă (numite în continuare pagini);
 fiecărui program i se alocă un anumit număr de pagini succesive, astfel încât zona formată
să-l poată cuprinde;
 alocatorul poate utiliza două tab ele pentru gestiunea paginilor și minimizarea reacoperirii
lor: tabela contorilor de reacoperire și tabela de ocupare. În tabela contorilor de
reacoperire, fiecărei pagini din memoria operativă i se asociază o intrare pentru a
înregistra numărul proceselor care utilizează această pagină. Tabela de ocupare indică, în
orice moment, pentru fiecare pagină indicatorul procesului care o ocupă.
Pentru utilizarea acestei metode, hardware -ul trebuie să fie prevăzut cu registre limită
(superioară și inferioară) de pr otecție a paginilor și cu un mecanism de relocare a programelor.
Introducerea priorităților atașate proceselor conduce la creșterea eficienței metodei.

3. Metoda reacoperirii întârziate : La baza acestei metode stau următoarele principii [1]:
 memoria operati vă este divizată în pagini, cărora hardware -ul le asigură o protecție
individuală;
 alocatorul administrează o tabelă de ocupare, al cărei număr de intrări este egal cu
numărul paginilor din memoria centrală; fiecare intrare conține identificatorul procesul ui
care ocupă pagina în momentul respectiv;
 fiecărei pagini îi este asociat un bit de modificare, poziționat de hardware ori de câte ori
pagina respectivă a fost implicată într -o operație de scriere și repus la zero de alocator în
momentul reacoperirii ac estei pagini;
 nivelul hardware al sistemului de calcul este prevăzut cu un registru special, actualizat de
nucleu, care conține în orice moment indicatorul procesului ACTIV .
Ideea algoritmului este următoarea: Inițial, orice proces este lansat cu un număr minim de
pagini rezidente în memoria centrală. În cursul execuției, pe măsură ce adresele sunt generate,
componenta hardware verifică prezența paginii corespunzătoare în memoria principală. Dacă
pagina adresată este prezentă, atunci execuția continuă; în caz contrar este provocată o deviere
a cărei tratare antrenează încărcarea paginii cerute. Încărcarea este făcută imediat, dacă pagina
vizată este liberă sau dacă nu a fost modificată de către alt proces în cursul ultimei utilizări.
Din contră, dacă proces ul care ocupă această pagină a modificat -o în cursul execuției sale,
atunci alocatorul salvează pagina în cauză, în memoria secundară, înainte de reutilizarea ei.

4.4.2. Memoria virtuală

Odată cu creșterea dimensiunii programelor utilizatorilor și a volumului de date pe care acestea le
manipulează, au apărut noi mecanisme de adresare a memoriei și noi metode de gestiune a resurselor.
Restricția de continuitate a unui program în memoria centrală, impusă de metodele prezentate anterior,
a fost înlăturat ă prin introducerea noțiunilor de segmentare și paginare. Noțiunea de bază utilizată în
legătură cu alocarea memoriei pentru programe de dimensiune foarte mare este memoria virtuală.
Acest termen este, de regulă, asociat cu capacitatea de a adresa un spați u de memorie mai mare decât
cel disponibil.

Adresele generate de un program sunt numite adrese virtuale și formează spațiul de adrese virtual. La
sistemele de calcul fără memorie virtuală, adresele virtuale identifică adrese din memoria fizică. În
sisteme le cu memorie virtuală, adresele virtuale sunt prelucrate de cuplorul de gestiune a memorie
(memory management unit – MMU) care le translatează în adrese fizice. Memoria poate fi gestionată
prin manipularea de zone de dimensiuni fixe (alocarea paginată) sa u variabile (alocarea segmentată).
Se poate utiliza, de asemenea, o combinație a celor două metode (alocare segmentată și paginată).

176
A. Alocarea paginată

Metodele de gestiune din această categorie, presupun că memoria operativă (inițial de capacitate M)
este împărțită (odată pentru totdeauna) într -un anumit număr de zone de dimensiune fixă, numite
pagini de memorie fizică (pf). În acest context, descriptorii care definesc starea ocupării memoriei
operative sunt memorați într -un tabel ce păstrează evidenț a alocării memoriei operative. De asemenea,
instrucțiunile și datele fiecărui program sunt împărțite în zone de lungime fixă, numite pagini virtuale
(pv). Paginile virtuale se păstrează în memoria secundară.

Fiecare proces are propria lui tabelă de pagini , care conține adresa fizică a paginii virtuale (când
aceasta este prezentă în memorie). La încărcarea unei noi pagini virtuale, aceasta se depune într -o
pagină fizică liberă. Are loc, astfel, o proiectare a spațiului virtual peste cel real.

PAGINI VIR TUALE
indice: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
adresă de start: 0 4 8 12 16 20 24 28 32 36 40 44 48 52 56 60
cod: 2 3 1 0 6 x x 5 x x 7 x 4 x x x
PAGINI FIZICE
indice: 0 1 2 3 4 5 6 7
adresa de start: 0 4 8 12 16 20 24 28

Exemplu: Un sistem de calcul generează adrese de 16 biți (de la 0 la 64KB). Acestea sunt adrese
virtuale. Presupunem că el dispune doar de 32KB memorie fizică. Rezu ltă că, programele mai lungi de
32KB nu pot fi încărcate complet în memorie. Spațiul de adrese virtual va fi împărțit în pagini virtuale,
iar memoria fizică în pagini fizice. Presupunem că paginile virtuale și paginile fizice au dimensiunea
de 4KB. Vom obț ine 16 pv și 8 pf. Presupunem că tabela de pagini a procesului asociat programului
nostru este cea din figura 2.2.6 (codul ‘x’ înseamnă pagină nealocată, iar codul numeric specifică
indicele paginii fizice asociate). Astfel, toate adresele virtuale între 0 și 4095 vor fi asociate adreselor
fizice cuprinse între 8192 și 12287.

Transformarea adreselor virtuale în adrese reale se realizează în mod automat, iar încărcarea paginilor
virtuale în pagini fizice se face la cerere (în urma primului acces) sau printr -o operație de preîncărcare.

Paginația poate fi definită pe unul sau mai multe nivele, adică tabela de pagini asociază unei pagini
virtuale indexul paginii fizice (asociere directă) sau adresa unei tabele de indirectare (asociere
indirectă). Această tabel ă poate duce la indexul paginii fizice sau la o altă tabelă de indirectare. În
primul caz paginația este cu două nivele, iar în celălat caz paginația este cu mai mult de două nivele.
Metoda paginației cu mai multe nivele are avantajul de a păstra în memori e, numai grupul de pagini
necesar la un anumit moment.

Pagina fizică corespunzătoare unei pagini virtuale este determinată pe baza adresei virtuale. În cazul
paginației cu mai multe nivele, adresa virtuală are mai multe câmpuri, fiecare câmp fiind folosit pentru
a specifica indicele intrării într -o anumită tabelă. Vom exemplifica această tehnică în cazul paginației
cu două nivele. Presupunem că adresa virtuală are 32 de biți, grupați în trei componente: IND1 (10
biți), IND2 (10 biți) și DEPLASAMENT (12 biț i). Această structură este prezentată în figura 2.2.7 (a).
Figura 2.2.7 (b) prezintă tabela paginilor, organizată pe două nivele. Primul nivel este reprezentat de o
tabelă cu 1024 de intrări (T1) care corespund celor 10 biți ai câmpului IND1. Numărul de bi ți ai
fiecărei intrări din tabela T1 determină numărul tabelelor celui de -al doilea nivel. O tabelă din acest
nivel va fi notată prin T2. Conform câmpului IND1, se va alege din T1 o intrare, iar valoarea
memorată la această intrare va indica adresa unei ta bele de tip T2. Câmpul IND2 al adresei virtuale va

177
conduce la intrarea corespunzătoare din tabela de tip T2, unde va fi memorată o anumită valoare.
Această valoare împreună cu câmpul DEPLASAMENT vor genera adresa paginii fizice.

Alegerea unei strategii de alocare a paginilor fizice are o mare influență asupra performanțelor. Dacă
la un moment dat este activ un singur proces, atunci orice deviere pagină -absentă creează un gol în
activitatea procesorului. Pentru a crește eficiența d e exploatare a procesorului trebuiesc create mai
multe procese, adică în momentul inițializării sistemului se încarcă un număr minim de pagini
(necesare lansării execuției) pentru mai multe procese. Astfel, în timp ce se încarcă o pagină cerută de
un anumi t proces, procesorul poate lucra pentru alt proces care era în starea ÎNTRERUPT . Numărul
proceselor aflate în starea ÎNTRERUPT nu trebuie să depășească o anumită limită; cu cât acest număr
este mai mare, cu atât numărul de pagini fizice alocate unui proces este mai mic, ceea ce duce la
creșterea ratei devierilor pagină -absentă.

Sistemul poate să utilizeze două metode de alocare a paginilor: preîncărcare sau pagină la cerere.
Preîncărcarea presupune aducerea paginilor în memoria operativă înainte de prima u tilizare a acestora.
În schimb, în acest caz există riscul de a încărca (în absența unui model realist de comportare a
programelor) pagini inutile. Metoda de încărcare pagină la cerere nu încarcă o pagină în memoria
centrală decât în momentul în care se f ace o referință la aceasta. Absența unei pagini adresate provoacă
o deviere, a cărei tratare are drept rezultat încărcarea paginii cerute. În cazul în care nu mai există
pagini fizice disponibile pentru a satisface cererea, trebuie reacoperită o pagină rez identă în memoria
operativă. Algoritmul care determină pagina care trebuie reacoperită se numește algoritm de înlocuire.

Metoda ideală ar consta în reacoperirea paginii care va fi utilizată cel mai târziu; realizarea acestei
soluții este foarte dificilă d eoarece, de obicei, nu se poate cunoaște comportarea viitoare a unui proces.

Algoritmii de înlocuire, imaginați și utilizați în practică, se deosebesc prin informațiile, pe care le iau
în considerare, relative la utilizările anterioare ale paginilor fizi ce și/sau comportările viitoare ale
proceselor.

Există trei clase de algoritmi de înlocuire .

1. Algoritmi care nu utilizează nici un fel de informații privind comportarea anterioară sau
viitoare a proceselor:
 Algoritmul RAND utilizează o regulă statică. El presupune că toate paginile au acceași
șansă de a fi adresate și în consecință alege la întâmplare pagina ce va fi reacoperită. În
cazul în care spațiul virtual este mult mai mare decât capacitatea memoriei operative,
eficacitatea acestui algoritm crește; posibilitatea de a face o alegere proastă scade.
 Algoritmul FIFO înlocuiește pagina care a rămas cel mai mult timp în memoria
operativă. Din punct de vedere al implementării, algoritmul FIFO este preferabil; este mai
ușoară implementarea unui contor cicli c decât generarea numerelor aleatoare.

2. Algoritmi care anticipează următoarele referințe pe baza informațiilor extrase din istoria
procesului:
 Algoritmul LRU (Least Recently Used) înlocuiește pagina care nu a fost utilizată de cel
mai mult timp.
 Algoritmul LFU (Least Frequently Used) înlocuiește pagina care a fost utilizată de cele
mai puține ori. Acest algoritm folosește proprietatea programelor de a avea bucle; rezultă
că unele pagini sunt des utilizate, în timp ce altele sunt utilizate mai rar.
3. Algoritmi care se bazează pe cunoașterea comportării viitoare a procesului: Algoritmul MIN ,
singurul reprezentant al acestei clase, este o extensie a celor din clasa precedentă în sensul că
minimizează numărul de încărcări, dar presupune cunoscută comportarea viito are a procesului.

178
El funcționează astfel: dacă există pagini care nu vor mai fi utilizate, atunci se înlocuiește una
din ele; în caz contrar se înlocuiește pagina care va fi folosită cel mai târziu.

B. Alocare segmentată

Memoria segmentată este întâlnită numai în sisteme de calcul capabile să interpreteze partea de adresă
a unei instrucțini ca un cuplu (s, d), unde s este identificatorul (numele) unui segment, iar d este o
deplasare relativă în acest segment. Un segment este o suită de locații su ccesive de memorie, a cărui
dimensiune este variabilă, dar limitată de numărul de biți rezervați părții de deplasare din instrucțiunea
mașină. Alocarea memoriei operative prin zone continue de dimensiune variabilă, impune existența
unui mecanism de adresar e pentru a asigura relocarea dinamică a segmentelor. În acest context,
unitatea de alocare este segmentul.

Într-un sistem de calcul cu memorie segmentată, un program poate fi privit ca o mulțime de segmente
de cod și de date. Mai mult, fiecare proces are o tabelă de segmente și fiecare intrare din această tabelă
conține adresa de început a segmentului. Fiecare segment are o structură proprie care este aplicată,
prin funcția de asociere a adreselor, în memoria virtuală (spațiul segmentelor), pe durata sa de
existență. Alocatorul realizează proiecția spațiului segmentelor în memoria fizică, printr -o funcție de
translatare. Întrebarea care se pune este: cum și în ce moment trebuie realizată translatarea? Soluția
ideală este de a realiza translatarea adreselor în momentul execuției procesului. În momentul în care
este utilizată, orice adresă virtuală este tradusă prin funcția de translatare într -o adresă fizică. Funcția
de translatare furnizează adresa reală solicitată sau semnalează faptul că adresei virtuale n u i s -a
asociat încă o adresă a spațiului real. Acest mod de translatare a adreselor impune relocarea dinamică a
segmentelor, cu implicații asupra eficienței exploatării memoriei centrale.

Pentru realizarea relocării dinamice a segmentelor, hardware -ul trebuie să dispună de un mecanism de
adresare particular, obținut prin utilizarea registrelor de bază sau a paginației. Relocarea dinamică cu
registre de bază menține continuitatea fizică a segmentului relocat, spre deosebire de tehnica
paginației, care eli mină această restricție. Principiul relocării dinamice cu registre de bază este
următorul: Nucleul sistemului de operare utilizează un registru particular, numit registru de bază.
Conținutul acestuia se adaugă automat fiecărei adrese generate de procesor, iar rezultatul este utilizat
pentru adresarea memoriei operative. Astfel, un segment poate fi încărcat în orice secvență de locații.
Dacă registrul de bază conține adresa de fixare a unui segment în memoria principală, acesta va fi
utilizat în mod corect, cu condiția ca adresa sa virtuală, de origine, să fie zero. După încărcarea unui
segment în memoria centrală, acesta poate fi deplasat, dar reutilizarea este permisă numai după ce
nucleul sistemului de operare a actualizat registrul de bază cu noua sa adre să de început.
Folosirea mai multor registre de bază distincte, pentru segmentele de cod și de date, permite fixarea lor
independentă, ceea ce ușurează rezolvarea problemelor legate de partajarea segmentelor.

Existența unui mecanism de relocare dinamică ( obținut prin utilizarea registrelor de bază) facilizează
utilizarea unei strategii de alocare la cerere a zonelor de memorie operativă de dimensiune variabilă.
Este evident că, metodele de alocare cu zone de dimensiuni variabile introduc o fragmentare ext ernă,
manifestată, după un anumit timp, prin prezența a numeroase zone libere, de dimensiuni prea mici
pentru a satisface cererea în curs, dar a căror dimensiune totală o depășește pe cea a cererii. Pentru a
elimina această situație, zonele alocate pot fi compactate la o extremitate a memoriei centrale. Astfel
se va obține (la cealaltă extremitate) o zonă liberă a cărei dimensiune este egală cu suma dimensiunilor
zonelor libere existente. Prin această strategie este posibil ca după o compactare, la următoar ea cerere
situația să se repete. De asemenea, compactarea memoriei centrale poate fi inițiată la eliberarea
fiecărei zone ocupate. Această metodă de compactare consumă prea mult timp (fiind prea des
invocată).

179
C. Alocare segmentată și paginată

Ideea al ocării segmentate și paginate este aceea că alocarea spațiului pentru fiecare segment să se facă
paginat. Pentru aceasta, inițial fiecare proces aflat în starea ÎNTRERUPT are propria lui tabelă de
segmente. Apoi, fiecare segment încărcat în memoria operati vă are propria lui tabelă de pagini. Fiecare
intrare în tabela de segmente are un câmp care indică adresa de început a tabelei de pagini proprii
segmentului.

5. Gestiunea memorie externe. Sisteme de fișiere. Tehnici input -output

Colecțiile mari de date , dar și informațiile gestionate de sistemul de operare pentru realizarea funcțiilor
sale sunt stocate în memoria externă prin intermediul fișierelelor și a cataloagelor (directoarelor,
mapelor) de fișiere.

5.1. Gestiunea fișierelor

În cadrul unui volum extern, datele sunt grupate de către sistemul de operare în ansambluri de date
numite fișiere . Utilizatorul, prin programele sale, are acces – la un moment dat – numai la o parte dintr –
un fișier numită articol sau înregistrare . În general, un articol al unui fișier reprezintă o subdiviziune
a fișierului (un caracter, o secvență de caractere) ce constă din subgrupuri de date numite câmpuri.
Fiecare câmp al unui articol are o anumită valoare în fișier. O pereche (câmp, valoare) s e numește
cheie de articol . Este posibil ca într -un fișier să existe mai multe articole cu aceeași cheie, sau se pot
imagina chei care să nu existe în nici un articol. De asemenea, este posibil ca într -un fișier să existe
câmpuri cu valori care nu se repet ă. Un index este un câmp cu proprietatea că pentru oricare două
articole diferite ale fișierului, valorile câmpului sunt diferite.

Fiecare câmp al unui articol are un domeniu (mulțimea valorilor), precum și o lungime de
reprezentare . Suma lungimilor de re prezentare a câmpurilor, împreună cu lungimea altor zone
completate de sistemul de operare, determină lungimea de reprezentare a articolului. Dacă lungimea de
reprezentare este aceeași pentru toate articolele unui fișier atunci spunem că articolele sunt de format
fix. În caz contrar, înregistrările se numesc articole de format variabil și trebuie să existe un câmp în
care să fie înregistrată fie lungimea efectivă a articolului, fie un marcaj de sfârșit de înregistrare.

Fișierele pot fi organizate în mai mu lte moduri. Cel mai simplu mod de organizare este cel liniar, un
fișier fiind o colecție de caractere. Semnificația caracterelor nu este cunoscută sistemului de operare ci
este impusă de programele utilizatorilor. Unele fișiere pot fi privite ca o colecție de înregistrări de
lungime fixă. Astfel, sistemul de operare poate citi un articol și poate scrie (prin suprascriere sau
adăugare la sfârșitul fișierului) o înregistrare. Sistemele de operare mai vechi structurau fișierele în
articole de lungime fixă. Cel mai interesant mod de structurare a informației este organizarea
arborescentă. În acest caz, un fișier constă dintr -un arbore de înregistrări, de lungimi nu neapărat
egale, fiecare articol conținând, într -o poziție fixă, un câmp special numit index. Arbor ele este sortat
pe baza acestui index, pentru a permite accesul rapid la înregistrări.

Cele mai importante tipuri de acces la articolele unui fișier sunt: accesul secvențial și accesul direct.
Accesul secvențial la al n -lea articol dintr -un fișier presupune efectuarea în ordine a n -1 accese la
articolele cu numerele de ordine 1, 2, …, n -1. Accesul direct presupune existența unui mecanism de

180
obținere a articolului căutat fără parcurgerea secvențială a tuturor ar ticolelor care îl preced. Sunt
posibile două tipuri de acces direct:
 acces direct prin adresă (număr de ordine) . În acest caz programul utilizatorului furnizează
numărul de ordine al articolului, iar sistemul de operare întoarce articolul solicitat.
 accesu l direct prin conținut . Programul utilizatorului furnizează o cheie (câmp, valoare), iar
sistemul de operare întoarce articolul care îndeplinește condiția specificată.

Orice fișier este identificat printr -un specificator. Nu există reguli general valabile de specificare a
unui fișier. În general, referirea la un fișier se face printr -un șir de caractere, diferite de spațiu. Șirul de
caractere este structurat în mai multe zone, plasate de la stânga la dreapta astfel:
 Numele dispozitivului periferic (DP) sup ort al fișierului. Aceste nume sunt impuse de sistemul
de operare. Sistemul de operare UNIX face excepție de la această structurare deoarece orice
periferic este privit în UNIX ca un fișier obișnuit.
 Calea ce desemnează subcatalogul din cadrul suportului e xtern (disc, memorie flash etc.) unde
se află fișierul căutat.
 Numele fișierului (un șir de caractere, dependente de sistemul de operare) dat de creatorul
fișierului.
 Extensia sau tipul fișierului (un șir de caractere, dependente de sistemul de operare) da t tot de
creator. Fiecare sistem de operare poate cere utilizarea unor extensii speciale
 Alte informații. De exemplu, versiunea fișierului este un câmp ce ar putea fi utilizat pentru a
diferenția mai multe versiuni ale aceluiași fișier.

În plus, sistemul de operare poate asocia unui fișier și alte caracteristici, printre care: modul de
protecție și utilizatorii care au acces la fișier; biți indicatori ai statutului fișierului (fișier cu acces
numai în citire, fișier ascuns, fișier sistem, fișier arhivat et c); poziția și lungimea indexului; data
creerii, a ultimei modificări și a ultimului acces; dimensiunea curentă și dimensiunea maximă la care
poate ajunge. Aceste caracteristici suplimentare se numesc atribute .

Gestiunea fișierelor în cadrul unui sistem d e operare este realizată prin intermediul unui ansamblu de
subprograme, numit sistemul de gestiune a fișierelor (SGF), care asigură legătura între utilizatori și
dispozitivul periferic care realizează operațiile asupra fișierelor.

Acțiunile SGF pot fi st ratificate astfel: acțiuni la nivel de articol; acțiuni la nivel de fișier și acțiuni la
nivel de volum. Unele sisteme de operare oferă și operații la nivel de câmp.

Acțiuni la nivel de articol:
 Citire (Read) – Datele sunt citite din fișier începând cu p oziția curentă. Solicitantul unei
operații de citire trebuie să indice cantitatea de date necesară și o zonă de memorie unde să
fie depuse.
 Scriere (Write) – Datele sunt înregistrate în fișier începând cu poziția curentă. Dacă
poziția curentă este la sfâr șitul fișierului atunci datele sunt scrise în continuare. Dacă
poziția curentă este situată înainte de sfârșitul fișierului, datele existente sunt pierdute
deoarece datele noi se scriu peste cele vechi. De asemenea, solicitantul unei operații de
scriere tr ebuie să specifice adresa unei zone de memorie de unde se vor lua datele, precum
și lungimea zonei de scris.
 Adăugare la sfârșitul fișierului (Append) – Prin această operație articolele pot fi scrise
numai la sfârșitul fișierului.
 Poziționare (Seek) – Într-un fișier cu acces direct, această operație permite specificarea
directă a unui anumit articol. După poziționare, articolul poate fi citit, sau în această
poziție se poate scrie un articol.

181

Acțiuni la nivel de fișier:
 Deschiderea unui fișier (Ope n) – Acțiunile SGF care au loc înaintea primului acces la un
fișier sunt realizate prin intermediul operației de deschidere ( open ) a fișierului respectiv.
Prin operația Open , sistemul de operare este informat că un anumit fișier va deveni activ.
În general , se cere ca înaintea primei citiri sau scrieri, fișierul să fie deschis. Prin
deschidere se face legătura între informațiile de identificare a fișierului și variabila din
program prin intermediul căreia utilizatorul se referă la fișier. Operația de deschi dere
poate fi executată atât asupra fișierelor care există deja pe disc cât și a fișierelor noi. În
funcție de condițiile de deschidere, prin operația Open se parcurg următoarele etape:
o Pentru un fișier existent:
 se realizează legătura între variabila di n program ce indică fișierul,
suportul pe care se află păstrat și descriptorul de fișier aflat pe disc;
 se verifică, dacă utilizatorul are drept de acces la fișier.
o Pentru un fișier nou, care urmează a fi creat, este necesară alocarea spațiului pe
disc p entru memorarea viitoarelor articole ale fișierului;
o Se alocă memorie internă pentru zonele tampon necesare accesului la fișier;
o Completează o structură de date numită blocul de control al fișierului ( File
Control Bloc ) cu informațiile obținute în etapele anterioare.
 Închiderea fișierelor (Close) – Prin această operație, fișierul încetează de a mai fi activ.
Se actualizează informațiile generale despre fișier: lungime, data ultimului acces etc.
Pentru fișiere nou create se adaugă o nouă intrare în catalogul discului. Pentru toate
fișierele, se adaugă când este cazul, marcajul EOF după ultimul articol al fișierului. Se
golesc (dacă e cazul) zonele tampon. De asemenea, se eliberează spațiul de memorie
ocupat de FCB și zonele tampon.
 Creare (Create) – Fișierul este creat fără date. Scopul operației constă în stabilirea unor
atribute ale fișierului. Uneori se realizează o anumită formatare în vederea încărcării
ulterioare a datelor.
 Ștergere (Delete) – În momentul în care un fișier nu mai este necesar, el poate fi șters.
Astfel, spațiul ocupat pe disc se eliberează și se elimină intrarea din catalogul care conține
descriptorul fișierului.
 Fixare atribute – Prin această operație anumite atribute ale fișierului sunt stabilite la
anumite valori (de exemplu, comanda UNIX chmod pentru stabilirea atributelor de
protecție).
 Aflare atribute – Uneori, într -o aplicație, utilizatorul are nevoie de atributele unui fișier.
De exemplu, în sistemul de operare UNIX, programul make (utilizat în elaborarea
aplicațiilor software), a re nevoie de atributele fișierelor cu care lucrează. Mai precis, make
examinează timpul ultimului acces pentru scriere în fișierele în cauză și efectuează
anumite operații numai asupra fișierelor care au fost modificate de la ultima utilizare de
către make.
 Redenumire (Rename) – Această operație schimbă numele unui fișier existent.

Alte acțiuni:
 Copierea unui fișier în alt fișier (Copy) – Dacă este necesară o copie fizică a fișierului sursă
(emițător) într -un fișier destinație (receptor) atunci se utilizează operația de copiere.
 Listarea unui fișier (type sau print) – Această operație este un caz particular de copiere.
Numai fișierele de tip text pot face obiectul acestei operații. Fișierul destinație este ecranul
terminalului (type) sau imprimanta (print).
 Concatenarea mai multor fișiere (cat sau copy+) – Operația este o generalizare a copierii. De
exemplu, în sistemul de operare UNIX comanda

182
$ cat f1 f2 f3 > f4
realizează concatenarea fișierelor f1, f2 și f3 și depune rezultatul în fișierul f4.
 Compararea a două fișiere : este realizată prin parcurgerea secvențială a celor două fișiere.
 Sortarea articolelor unui fișier (sort) – Prin această operație articolele fișierului inițial sunt
ordonate conform anumitor specificații, iar rezultatul operație i se depune într -un nou fișier.
 Operații de filtrare a fișierelor – Un filtru este un program care citește un fișier și creează
altul pe baza unor specificații. De exemplu, fișierul rezultat se poate obține prin: sortare,
eliminare de articole cu un anumit conținut, prelucrarea unui fișier conform unor machete etc.
 Compresia datelor dintr -un fișier – Această operație se realizează asupra fișierelor de
dimensiune mare care urmează a fi arhivate. Există multe tehnici de compresie, iar pentru
fiecare program d e compresie există un program care reface informația inițială.

5.2. Gestiunea cataloagelor

Din punct de vedere logic, cataloagele pot fi organizate pe nivele (unul sau mai multe), arborescent
sau sub forma unui graf aciclic (un digraf fără cicluri).

Structura cu un singur nivel reprezintă cel mai simplu mod de organizare. Toate fișierele de pe disc
sunt înregistrate în același catalog. O astfel de structură impune o serie de restricții asupra lungimii
numelui fișierelor și a numărului maxim de fișiere. Numărul de intrări în catalog este limitat de
dimensiunea catalogului. Structura cu două niveluri presupune existența unui catalog principal (master
file directory) care conține câte o intrare pentru fiecare utilizator al sistemului. Această intrare este un
pointer către un catalog utilizator. Intrările într -un catalog utilizator descriu fișierele utilizatorului
respectiv. Modul de organizare arborescentă extinde în mod natural structura cu două nivele.

Un fișier catalog se deosebește de un fișier obișnu it numai prin informația ce o conține. Un catalog
conține lista numelor și adreselor fișierelor subordonate lui. În plus, fiecare catalog are două intrări cu
nume speciale și anume: "." (punct) – se referă la însuși catalogul respectiv și ".."(punct -punct) care se
referă la catalogul părinte. Fiecare volum extern conține un catalog principal numit catalog rădăcină
(ROOT). Prin intermediul unor programe specializate (comenzi), un utilizator poate: schimba
catalogul curent (cd); poziționa unele drepturi de ac ces (chmod); crea subcataloage subordonate
catalogului curent (mkdir); șterge un subcatalog creat anterior (rmdir); afișa calea de acces de la
catalogul ROOT la cel curent (pwd) etc.

Utilizatorul poate specifica o cale de acces în două moduri: absolut și relativ. Structura de graf aciclic
este utilă atunci când anumite fișiere sau cataloage trebuie să fie accesibile din mai multe cataloage
(fișierele respective sunt resurse partajate).

Orice sistem de operare orientat pe gestiunea cataloagelor pune la dispoziție apeluri sistem pentru
lucrul cu cataloage. Cele mai importante operații asupra cataloagelor sunt: creare, ștergere, deschidere,
închidere, citire, redenumire, creare de legături și eliminare de legături:
 Crearea unui catalog (CREATEDIR) înseamnă crearea unui fișier cu două intrări standard:
"." și "..". Această operație este realizată folosind o comandă a sistemului de operare (mkdir
(UNIX și MS -DOS) sau md (MS -DOS)).
 Ștergerea (DELETEDIR) unui catalog înseamnă ștergerea unei intrări din catalogu l părinte.
Un catalog poate fi șters numai dacă în afară de intrările standard nu mai conține și alte intrări.
 Deschiderea unui catalog (OPENDIR) este necesară pentru realizarea operației de citire a
fișierului catalog. De exemplu, un program special (DIR în MS -DOS, ls în UNIX etc.) va
deschide catalogul și va citi numele fișierelor din catalog.

183
 Închiderea unui catalog (CLOSEDIR) este necesară dacă nu mai sunt operații de executat
asupra unui catalog descris anterior. Prin această operație, zona de memorie alocată la
deschiderea fișierului catalog va fi eliberată.
 Prin operația de citire (READDIR) se va obține intrarea următoare din fișierul catalog supus
citirii.
 Cum cataloagele sunt similare fișierelor, acestea pot fi redenumite în același mod. Este
realiz ată astfel operația de redenumire a unui catalog (RENAMEDIR).
 Prin crearea unei legături (operația LINK) , un fișier poate să apară în mai multe cataloage.
Pentru realizarea acestei operații se specifică atât fișierul cât și calea ce indică catalogul care
va deveni părinte al fișierului. În acest catalog se va introduce o intrare specială pentru fișierul
în cauză. Prin acest mecanism, un fișier este vizibil în mai multe cataloage.
 Prin ștergerea unei legături (operația UNLINK) este înlăturată intrarea specia lă din catalog;
c|nd catalogul curent este chiar catalogul în care s -a creat fișierul și acesta nu mai este legat la
nici un alt catalog, atunci fișierul va fi șters.

O altă operație constă în montarea și demontarea unui sistem de fișiere. Operația de montare constă în
conectarea unui sistem de fișiere de pe un anumit disc, la un catalog vid existent în sistemul de fișiere
de pe discul implicit. Operația de demontare are efect invers.

5.3. Drivere de Intrare/Ieșire (I/E)

Un driver de I/E este o componen tă sistem sau utilizator care asigură interfața dintre nucleu și unul sau
mai multe DP. Fiecare driver este un program separat, scris în limbaj de asamblare, C etc., și compilat
într-un fișier executabil. Un driver trebuie să îndeplinească următoarele fun cții: 1) recepționează și
prelucrează întreruperile generate de DP; 2) inițiază operațiile I/E solicitate driver -ului de către nucleu;
3) forțează terminarea unor operații I/E în curs de desfășurare; 4) efectuează alte funcții (specifice
fiecărui DP), în c azul recuperărilor din avarie sau expirării unui interval de timp.

Un driver I/E poate fi rezident sau încărcabil. Un driver rezident constituie o parte componentă a
nucleului sistemului de operare, incorporată la generarea (prin compilare) sistemului. Un driver
nerezident constituie o extensie a nucleului, ce poate fi încărcată sau eliminată dinamic din memorie.
În cazul în care un utilizator dorește să conecteze la configurația sa hardware un DP pentru care nu
există un driver, este necesară scrierea ace stuia de către utilizator.

Un driver poate fi de tip caracter sau de tip bloc. El nu poate asigura, în același timp, funcții de tip
caracter și funcții de tip bloc. Considerat a fi o parte a nucleului, un driver posedă un context propriu,
poate inhiba ș i valida sistemul de întreruperi, putând sincroniza și accesul la structurile de date ale
sistemului de operare împreună cu alte componente ale nucleului. Un driver poate suporta mai multe
DP, fiecare cu mai multe unități, toate lucrând în paralel. În aces t caz însă, este necesară și o
sincronizare internă.

Pentru rezolvarea funcțiilor de mai sus, un driver poate avea mai multe puncte de intrare. De exemplu,
un driver poate avea 5 puncte de intrare: tratarea intreruperilor, inițierea unei operații I/E, tr atarea
expirării timpului, terminarea forțată și recuperarea din avarie. Tratarea de întrerupere este lansată
atunci când un DP, activat de driver, termină o operație I/E generând o întrerupere către procesor.
Legătura DP -driver este directă, prin intermed iul vectorilor de întrerupere. Inițierea unei I/E este
activată de către nucleu atunci când există cereri I/E în coada de așteptare a driver -ului, semnalizându –
i acestuia necesitatea preluării lor. Lansarea operației I/E se face prin depunerea informațiil or necesare
în registrele adaptorului (cuplorului) unui DP. Pentru tratarea expirării timpului se utilizează un

184
contor de numărare a timpului inițializat la inițierea unei operații I/E. Dacă funcția nu se termină în
intervalul de timp specificat, nucleul SO apelează driver -ul pe această intrare pentru ca el să poată lua
o decizie. Terminarea forțată a unei I/E este activată de către nucleu atunci când, în anumite
împrejurări, e necesar ca driver -ul să termine o operație I/E lansată, dar necompletată. Recu perarea din
avarie este inițiată de nucleu în trei cazuri distincte: la restabilirea tensiunii de alimentare; la
inițializarea sistemului, respectiv la încărcarea în memorie a unui driver încărcabil. În aceste cazuri se
efectuează o reinițializare funcțion ală a driver -ului pentru reluarea corectă a lucrului.

6. Protecție și securitate

Sistemele de operare de tip UNIX fiind sisteme multiutilizator și time -sharing, impun existența unui
utilizator special numit administrator de sistem (root), care ține evide nța utilizatorilor, stabilește
parolele și drepturile de acces și creează cataloagele asociate utilizatorilor. Pentru fiecare utilizator,
administratorul de sistem creează câte un catalog propriu, care poate conține atât fișiere ordinare
(programe sau date ), cât și subcataloage. Sistemele UNIX fac deosebire între litere mari și litere mici.
Toate fișierele sunt structurate în cataloage, organizate arborescent, în vârful ierarhiei (la rădăcina
arborelui) aflându -se catalogul rădăcină ( root) notat prin ‘/’. L a pornirea unui terminal, sistemul de
operare afișează mesajul/fereastra de conectare în vederea identificării utilizatorului. Utilizatorul va
introduce un nume (numai cu litere mici) urmat de ENTER. Apoi sistemul va solicita parola. Dacă
identificarea uti lizatorului s -a executat cu succes, sistemul de operare afișează fereastra de lucru sau un
șir de caractere numit prompter. Se mai spune că utilizatorul a deschis o sesiune de lucru. Închiderea
sesiunii curente se poate realiza folosind secvența CTRL+D sau Logout/Deconectare.

După deschiderea sesiunii, utilizatorul are acces la două grupe de fișiere: fișierele create de el însuși,
respectiv fișierele furnizate de sistem drept comenzi sistem. Comenzile sistemelor de tip UNIX sunt
foarte variate: comenzi de administrare, comenzi de dezvoltare de programe, comenzi operator,
comenzi pentru comunicație în rețea etc. Cele mai importante comenzi operator sunt:
 cd (schimbarea catalogului curent);
 pwd (afișarea catalogului curent);
 mkdir (crearea unui catalog);
 rm (ștergerea unui fișier);
 mv (redenumirea unui fișier);
 rmdir (eliminarea unui catalog);
 ls (listarea cuprinsului unui catalog) etc.

Pentru dezvoltarea de programe, sunt disponibile editoare de texte (vi, ed etc.), compilatoare (cc, cp
etc.), utilitare div erse.

Pentru asigurarea protecției fișierelor și cataloagelor se utilizează mecanisme specifice care să
restricționeze accesul utilizatorilor neautorizați. O primă măsură de protecție a datelor o reprezintă
drepturile de acces la fișier. Cât un utilizator nu are drepturi de administrator pe un anumit sistem de
calcul, acel utilizator se supune drepturilor de acces la fișiere pentru operații precum: citire, scriere,
executare, modificare, respectiv ștergere.

Există două metode mai întâlnite pentru definire a drepturilor de acces la fișiere: drepturi de acces la
nivel de utilizator/grup, respectiv acces prin liste – Access Control Lists (ACL). Prima metodă
(drepturi de acces la nivel de utilizator/grup) constă în definirea unor drepturi pentru următoarele
entități: posesorul unui fișier ( user); grupul care deține fișierul ( group); toți ceilalți utilizatori ( others).
Un utilizator se poate afla în mai multe grupuri. Această metodă este cea mai folosită cale pentru

185
definirea drepturilor de acces în UNIX. Metoda oferă un nivel de protecție suficient pentru majoritatea
situațiilor. În cadrul sistemului cu liste de acces, unui fișier i se pot asocia mai mulți utilizatori și/sau
grupuri de utilizatori. Pentru fiecare dintre aceste entități pot fi configurate mai mu lte tipuri de
drepturi. Sistemele de fișiere de tip Unix care au implementate standard drepturile de acces la nivel de
user/grup au la bază următorul set de drepturi de acces: citire (read) – deschidere și citire de fișiere,
scriere (write) –creare și scri ere de fișiere, respectiv executare (execute) – executare fișiere/intrare în
directoare.

Utilizatorul cu drepturile cele mai mari este „root” -ul. De regulă nu se folosește root ci utilizatorul
normal. Vizualizarea drepturile de acces pentru un anumit fiș ier se realizează prin utilizarea comenzii
ls cu parametrul -l (sau a comenzii ll). Prin chmod se permite modificarea drepturilor, iar prin chown
se permite modificarea proprietarului și a grupului căruia îi aparține respectivului fișier.

7. Interfețe utilizator

Comunicarea dintre sistemul de operare și utilizator se realizează prin intermediul meniurilor sau
pictogramelor (disponibile prin intermediul interfețelor grafice), respectiv prin dialog text în cadrul
modului linie de comandă (fereastra termi nal).

8. Exerciții și probleme

1. Ce este un sistem de operare?
a. ansamblul elementelor hardware incorporate în calculator
b. ansamblul elementelor software incorporate în calculator
c. un program sau un grup de programe care asigură exploatarea eficientă a resurselor
hardware și software ale unui sistem de calcul
d. kernelul

2. Un sistem de operare se mai numește
a. software de bază
b. compilator
c. editor de text
d. kernel

3. Cea mai importantă tehnică de exploatare optimă a procesorului intro dusă în sistemele cu procesare
pe loturi:
a. partajare a timpului
b. multiprogramare
c. programare paralelă
d. executare inlanțuită

4. Un sistem de operare care permite comunicarea permanentă între utilizator și sistemul de calcul este
un sistem cu
a. mul tiprogramare
b. programare paralelă
c. pelucrare pe loturi

186
d. partajare a timpului

5. Care sunt cele mai importante obiecte gestionate de un sistem de operare?

6. Ce este un sistem cu multiprogramare?

7. Enumerați trei caracteristici principale ale unui sistem de operare

8. Care sunt stările proceselor?

9. Descrieți principalele operații asupra proceselor

10. Descrieți tranzițiile posibile ale unui proces pe timpul duratei sale de viață.

11. Particularități ale proceselor UNIX.

12. Apelul fork();

13. Care sunt condițiile pe care trebuie să le satisfacă soluția corectă a problemei excluderii mutuale?

14. Metoda variabilei poartă. Descriere. Caracteristici

15. Metoda alternării. Descriere. Caracteristici

16. Metoda lui Peterson. Descriere. Caracte ristici

17. Metoda semafoarelor. Descriere. Caracteristici

18. Mecanisme de sincronizare în sisteme de tip UNIX

19. Funcții UNIX care permit sincronizarea controlată de utilizator

20. Mecanismul producător -consumator

21. Criteriile pe care trebuie să le satisfacă un algoritm ideal de planificare

22. Strategia FIFO pentru planificarea proceselor

23. Strategia LIFO pentru planificarea proceselor

24. Strategia " cel mai scurt timp de executare "

25. Strategia "Round Robin"

26. Alocarea prin reciclare b azată pe mai multe fire de așteptare

27. Metoda camarazilor. Descriere

28. Metoda numerelor lui Fibonacci

29. Metoda "Alocarea integrală și permanentă"

187

30. Metoda "Utilizator unic și rezident"

31. Utilizarea partițiilor variabile

32. Metoda transferu lui minim

33. Metoda suprapunerii minime

34. Metoda reacoperirii întârziate

35. Alocarea paginate

36. Clase de algoritmi de înlocuire a paginilor

37. Alocare segmentată

38. SGF: Acțiuni la nivel de articol

39. SGF: Acțiuni la nivel de fișier

40. SGF: Acțiuni la nivel de catalog

41. Drepturi de acces

42. Protecția fișierelor

43. Explicați diferența între așteptare activă și blocare.

44. Examinați algoritmul lui Peterson și arătați cum funcționează această schemă dacă ambele procese
apelează sim ultan procedura intrare_în_secțiune_critică.

45. Descrieți modul de implementare a mecanismului semafoarelor folosind dezactivarea
întreruperilor.

46. O generalizare a problemei producătorului și consumatorului o constituie problema cititorilor și
scriitorilor. Se presupune că există două tipuri de procese: cititor și scriitor. Ele utilizează în comun o
aceeași resursă, de exemplu un fișier. Un proces scriitor modifică conținutul fișierului, iar unul cititor
consultă informațiile din el. Orice proce s scriitor are acces exclusiv la resursă, în timp ce mai multe
procese cititor pot avea acces simultan la ea. Să se descrie o soluție de rezolvare a acestei probleme
folosind semafoare.

47. Cei mai mulți algoritmi "round -robin" utilizează o cuantă de dime nsiune fixă. Argumentați de ce ar
fi necesară o valoare mică a cuantei. Dar o valoare mare a cuantei?

48. Cinci procese A, B, C, D, E sunt create în același timp. Se estimează ca ele necesită execuții
complete de 10, 6, 2, 4 și 8 secunde. Prioritățile acestora sunt 3, 5, 2, 1 și 4, valoarea 5 desemnând o
prioritate mare. Ignorând timpul necesar trecerii de la un proces la altul, arătați evoluția proceselor
folosind algoritmul round -robin cu cuanta 1.

188
49. Cinci procese A, B, C, D, E sunt create în acela și timp. Se estimează ca ele necesită execuții
complete de 10, 6, 2, 4 și 8 secunde. Prioritățile acestora sunt 3, 5, 2, 1 și 4, valoarea 5 desemnând o
prioritate mare. Ignorând timpul necesar trecerii de la un proces la altul, arătați evoluția proceselor
folosind algoritmul de planificare pe bază de prioritate.

50. Cinci procese A, B, C, D, E sunt create în același timp. Se estimează ca ele necesită execuții
complete de 10, 6, 2, 4 și 8 secunde. Prioritățile acestora sunt 3, 5, 2, 1 și 4, valoarea 5 desem nând o
prioritate mare. Ignorând timpul necesar trecerii de la un proces la altul, arătați evoluția proceselor
folosind algoritmul de planificare conform timpului minim de execuție.

51. Cinci procese într -un sistem cu multiprogramare au duratele de execut are completă de 9, 6, 3, 5 și
respectiv X secunde. Care este ordinea de executare a proceselor astfel încât timpul mediu de așteptare
în sistem să fie minim. Discuție în funcție de valorile lui X.

52. Un sistem de calcul utilizează metoda camarazilor pent ru gestiunea memoriei. Inițial el dispune de
un bloc de 256K la adresa 0. Presupunem că au loc cereri succesive pentru: 5K, 25K, 35K și 20K. Ce
blocuri au mai rămas, care este dimensiunea și adresa acestora?

53. Care este diferența dintre o adresă fizică și o adresă virtuală?

54. Care este diferența între segmentare și paginare?

55. Sistemele interactive de tip multiuser au introdus:
a. modul de lucru numit time -sharing;
b. modul de lucru numit pipe -line;
c. interfețele de tip linie de comandă sau grafic a;
d. toate trei.

56. Modul de lucru numit time -sharing:
a. combină interactivitatea cu multiprogramarea;
b. a apărut odată cu sistemele interactive de tip multiutilizator;
c. atât (a) cât și (b);
d. nici (a), nici (b).

57. Pentru conducerea directă, interactivă a unor aplicații precum procesele tehnologice și rezervările
de locuri sunt utilizate sistemele:
a. în timp real;
b. distribuite;
c. paralele.

58. Sistemele de operare care gestionează o arhitectură multiprocesor se numesc sisteme:
a. în timp real;
b. distribuite;
c. paralele.

59. Nucleul minimal al sistemului de operare care conține funcțiile de bază se numește:
a. microkernel;
b. microkernel sau micronucleu;
c. micronucleu.

60. Serviciile asigurate de sistemele de operare sunt servicii or ientate:

189
a. spre utilizatori (facilitarea utilizării sistemelor de calcul);
b. spre asigurarea operării eficiente a sistemului de calcul;
c. spre utilizatori și spre eficientizarea utilizării resurselor de calcul.

61. Serviciile oferite de sistemele de op erare utilizatorilor sunt:
a. executarea programelor, operațiile de intrare -ieșire, comunicarea aplicațiilor între ele, cu
calculatorul și utilizatorii, detectarea erorilor;
b. executarea programelor, comunicarea aplicațiilor între ele, cu calculatorul și utilizatorii,
detectarea erorilor
c. executarea programelor, operațiile de intrare -ieșire, detectarea erorilor;
d. operațiile de intrare -ieșire, comunicarea aplicațiilor între ele, cu calculatorul și utilizatorii,
detectarea erorilor.

62. Serviciile prin care sistemele de operare asigură utilizarea eficientă a resurselor de calcul sunt:
a. alocarea resurselor, contabilizarea resurselor;
b. protejarea sistemelor de calcul, securitatea sistemelor de calcul;
c. alocarea resurselor, contabilizarea resurselor, protejarea sistemelor de calcul, securitatea
sistemelor de calcul.

63. Caracteristicile sistemului de operare UNIX sunt:
a. time -sharing;
b. portabil;
c. universal;
d. toate trei.

64. Interfața dintre sistemul de operare UNIX și utilizatori se realizea ză prin intermediul:
a. unei componente hardware dedicate;
b. unui program special, numit shell;
c. kernelului.

65. Utilitarele sunt programe care pot realiza anumite prelucrări de bază prin apelarea secvențială a
rutinelor kernel și constau în principal din:
a. compilatoare și instrumente pentru dezvoltarea aplicațiilor;
b. instrumente pentru administrarea sistemului și evaluarea performanțelor;
c. comenzi pentru lucrul cu fișierele și directoarele/cataloagele;
d. toate cele de mai sus.

66. Sistemul de fișiere UNIX se caracterizează prin:
a. structura ierarhică;
b. protecția fișierelor;
c. tratarea unui periferic în același mod ca un fișier;
d. toate cele de mai sus.

67. În sistemul de operare UNIX întâlnim următoarele directoare/cataloage principale:
a. /bin, /dev, /lib, /usr;
b. /bin, /dev, /more, /usr;
c. /bin, /cat, /lib, /usr.

68. Principalele comenzi UNIX pentru lucrul cu fișiere sunt:
a. cat, copy, find, grep, more, mv, rm;
b. cat, cp, find, grep, more, mv;

190
c. toate cele de mai sus.

69. Princ ipalele comenzi UNIX pentru lucrul cu directoare/cataloage sunt:
a. mkdir, rmdir, cd, pwd, copy ;
b. mkdir, cd, pwd, mcopy ;
c. mkdir, rmdir, cd, pasw, copy.

70. Principalele comenzi UNIX pentru controlul proceselor sunt:
a. kill, id;
c. id, chmod, kill;
b. kill, ps;

71. Comanda ls din sistemul de operare UNIX:
a. Afișează conținutul unui fișier
b. Afișează conținutul unui catalog/director
c. Afișează lista de fișiere și directoare/cataloage și spațiul ocupat de acestea
d. Afișează lista de partitii și spatiul liber și cel ocupat

72. Comanda ps din sistemul de operare UNIX:
a. Afișează conținutul unui fișier
b. Afișează conținutul unui catalog/director
c. Afișează lista de procese active
d. Afișează lista de partiții și spațiul liber

73. Comanda kill din sistemul de operare UNIX:
a. Șterge conținutul unui fișier;
b. Șterge directorul/catalogul curent;
c. Încheie programul curent;
d. Oprește procesul indicat.

74. MMU (memory management unit) este:
a. o unitate de stocare pentru memorie;
b. o compone ntă a sistemului de operare
c. un fișier sistem
d. un dispozitiv hardware

75. Sistemul de operare Minix are o arhitectură:
a. monolitică
b. modulară
c. microkernel
d. hibridă

76. Funcția pentru crearea unui nou proces în UNIX este:
a. execl
b. exec
c. fork
d. CreateProcess

77. Funcția pentru returnarea id -ului procesului părinte în UNIX este:
a. getppid()
b. getpid()

191
c. id()
d. ps()

78. Funcția pentru returnarea id -ului procesului fiu în UNIX este:
a. getcpid()
b. getcid()
c. fork()
d. cid()

79. Func ția pentru eliminarea unui obiect de tip fișier în UNIX este:
a. kill
b. unlink
c. remove
d. del

80. Adresele generate de MMU sunt adrese:
a. Reale
b. Virtuale
c. Logice
d. Liniare

81. Analizati textul de mai jos și explicați ce elemente moștenește procesul fiu.

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#define MAX_C 200
#define DIM 100
int main(void){
pid_t pid;
int i;
char X[DIM];
fork();
pid = getpid();
for (i = 1; i <= DIM; i++) {
sprintf(X, "LINIA procesului cu PID %d, val = %d \n",
pid, i);
write(1, X, strlen(X));
}
return 0;
}

82. Explicați fiecare din apelurile referitoare la sincronizarea proceselor create de codul de mai jos:

#include <unistd.h>
#include < sys/wait.h>
#include <stdio.h>
int main() {
int pID = getpid();
char progn[1024];
gets(progn);

192
int cid = fork();
if(cid == 0) {
execlp(progn, progn, 0);
printf("Lipseste programul %s \n", progn);
} else {
sleep (1);
waitpid(cid, 0 , 0);
printf("Program %s terminat \n", progn);
}
return 0;
}

83. Explicați fiecare din apelurile referitoare la sincronizarea proceselor create de codul de mai jos:

#include <signal.h>
#include <unistd.h>
#include <stdio.h>
int main() {
int pID = getpid();
int cid = fork();
if(cid == 0){
! sleep (5);
printf ( "Stop proces generat \n" );
exit (0); printf ( "Eroare!");
} else {
printf ( "Orice mesaj este bun. \n" );
char mesaj[10]; gets (mesaj);
if ( !kill(cid, SIGKILL) ) {
printf("Terminare proces fiu. \n");
} }
return 0;
}

84. Explicați fiecare din apelurile referitoare la comunicarea pipe din codul de mai jos:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
/* Scrie n mesaj(e) in fisierul f, cu pauza de o secunda intre
scrieri */
void scriitor (const char* mesaj, int n, FILE* f){
for (; n > 0; –n) {
fprintf (f, "%s \n", mesaj);
fflush (f); sleep (1);
}
} /* sf scriitor */

void cititor (FILE* f){
char X[1024];
while
(!feof (f) && !ferror (f) && fgets (X, sizeof (X), f) != NULL)
fputs (X, stdout);

193
} /*sf cititor */
int main (){
int pd[2];
pid_t pid;
pipe (pd); /* Creare pipe. */
pid = fork (); /* Generare proces fiu. */
if (pid == (pid_t) 0) {/* cod fiu * /
FILE* f;
close (pd[1]);
f = fdopen (pd[0], "r"); /* deschidere in citire */
cititor (f);
close (pd[0]);
}
else { /* cod parinte */
FILE* f;
close (pd[0]);
f = fdopen (pd[1], "w"); /* deschidere in scriere * /
scriitor (“Examen Licenta.”,5,f);
close (pd[1]);
}
return 0;
}

85. Explicați efectul secvenței de cod:

int k;
for (k = 0; k < 5; k++) fork();

86. Ce afișează programul:

int main( void){
int pid, n = 5;
pid=fork();
printf("Generat %d \n", pid);
if ( pid) n=2;
printf("n = %d \n",n );
return 0;
}

87. Pentru codul de mai jos se cere explicarea sarcinilor realizate precum și descrierea apelurilor
specifice gestionării proceselor.

#include <stdio.h>
#include <stdli b.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h> /* pt macro WIFEXITED si WEXITSTATUS */
int main (void){
pid_t pid; /* pentru fork() */
int stare; /* pentru wait() */
pid = fork ();/* se creeaza procesul fiu */
switch (pid) {

194
case 0: /* acesta este procesul fiu */
if (execlp ("ls", "ls", " -l", 0) < 0) {
perror ("exec");
exit (EXIT_FAILURE);
} * sf if */
exit (EXIT_SUCCESS);
break;
default: /* acesta este procesul parinte */
break;
}
if (wait (&stare) < 0) {
perror ("wait");
exit (EXIT_FAILURE);
}
if (WIFEXITED (stare) && WEXITSTATUS (stare) == 0) {
if (execlp ("df", "df", " -h", 0) < 0) {
perror ("exec");
exit (EXIT_FAILURE);
}
}
return 0;
}

88. Scrieți un program C care utilizează trei procese P0, P1 și P2 astfel: procesul părinte P0 citește
numere de la tastatură și le trimite la două procese fii P1 și P2, acestea calculează sumele și le trimit
inapoi la părintele P0, iar P0 adună cele două sume parțiale și afișează rezultatul final.

89 [8]. Se consideră fragmentul de cod de mai jos. Indicați liniile care se vor tipări pe ecran în ordinea
în care vor apărea, dacă apelul fork() se execută cu succes? Justificați răspunsul.

int main( ) {
int k;
for(k=0; k<2; k++) {
printf("%d: %d \n", getpid(), k);
if(fork() == 0) {printf("%d: %d \n", getpid(), k);
exit(0);}
}
for(k=0; k<2; k++) {wait(0);}
return 0;
}

90. Completați programul următor astfel încât procesul părinte să trimită, prin PIPE, variabila n
procesului fiu și să primească înapoi valoarea ei dublată.

int main() {
int n=1; if(fork() == 0) { ………..}
printf("%d \n", n);
return 1;
}

195
Bibliografie

1. Albeanu, G., Sisteme de operare, Ed. Petrion, 1996.
2. Albeanu, G., Arhitectura sistemelor de calcul, Editura FRM, 2007.
3. Ionescu, T., Saru, D., Floroiu, J. -W., Sisteme de operare, Ed. Tehnică, 1997.
4. Silberschatz, A., Galvin, P.B., Gagne, G., Operating Syst ems Concepts, Wiley & Sons, 2013
(ed. 9) .
5. Stuart, B., L., Principles of Operating Systems: Design & Applications, Thomson Learning,
2009.
6. Tanenbaum, A.S., Modern Operating Systems, Pearson Educational International, 2015(ed. 4) .
7. ***, Averian A., Sisteme de operare. Biblioteca Virtual/a, Universitatea Spiru Haret.
8. ***, Manual de Informatică pentru licență iunie și septembrie 2016,
http://www.cs.ubbcluj.ro/wp -content/uploads/Manual_Informatica_2016_RO.pdf.
9. ***, Examen de licență 2014, Model de subiect: In formatică, http://www.cs.ubbcluj.ro/wp –
content/uploads/Model_Subiect_2014_Informatica_Inf.pdf.

Similar Posts