Sunteți pe pagina 1din 5

Programarea Calculatoarelor

Laborator VII
Adrian Liţă / Ovidiu Grigore / Anamaria Rădoi
2015

1 Scopul laboratorului
Laboratorul VI al materiei Programarea Calculatoarelor are ca scop introduce-
rea tablourilor (vectorilor) şi a pointer-ilor.
În acest laborator se parcurg următoarele puncte:
• tablouri unidimensionale
• tablouri N-dimensionale

• adresa unei variabile


• pointeri
• operaţii cu pointeri

2 Desfăşurarea lucrării
Folosirea variabilelor reprezintă un lucru extrem de util în programare. Una din
principalele limitări ale variabilei este aceea că poate ţine minte doar o singură
valoare. Pentru cazurile în care este necesară memorarea mai multor valori a
fost introdus elementul numit tablou (sau array).
Un array reprezintă o colecţie de variabile de un anumit tip, plasate conti-
nuu (una după cealaltă) şi liniar în memorie. Cu ajutorul array-urilor putem
defini tipuri de date cum ar fi vectori sau matrice. Tabloul se declară în felul
următor:
1 t i p _ d a t e nume_tablou [ d i m e n s i u n e 1 ] [ d i m e n s i u n e 2 ] . . . [ dimensiuneN ] ;

Dacă tabloul are o singura dimensiune, acesta este se mai numeşte vector.

1
2.1 Pointeri
Pointerii sunt unul dintre cele mai folositoare tipuri de date într-un limbaj de
programare. Pointerii sunt variabile cu o singură particularitate: un pointer
poate ţine mine DOAR adresa unei variabile. Prin urmare, valoarea unui
pointer reprezintă o adresă şi nu ar trebui luată în considerare altfel!
1 char c ; // d e c l a r a r e a v a r i a b i l e i c
2 c h a r ∗ cp ; // d e c l a r a r e a p o i n t e r u l u i cp
3
4 c = ’a ’ ; // v a l o a r e a v a r i a b i l e i c d e v i n e c a r a c t e r u l ’ a ’
5 cp = &c ; // v a l o a r e a p o i n t e r u l u i cp d e v i n e a d r e s a v a r i a b i l e i c

Pointerul, indiferent la ce tip de date face referire, ocupă în memorie exact


atâţia octeţi cât ocupă o variabilă de tip int. Declararea unui pointer se face
prin operatorul *. Operatorul & aplicat unei variabile deja declarate retur-
nează adresa variabilei respective în memorie. Prin salvarea adresei variabilei
c (linia 5 din exemplul de mai sus) în pointerul cp, valoarea variabilei c rămâne
neschimbată. Adresa nu are nicio legătură cu VALOAREA.
1 char c ; // d e c l a r a r e a v a r i a b i l e i c
2 c h a r ∗ cp ; // d e c l a r a r e a p o i n t e r u l u i cup
3
4 c = ’a ’ ; // v a l o a r e a v a r i a b i l e i c d e v i n e c a r a c t e r u l ’ a ’
5 cp = &c ; // v a l o a r e a p o i n t e r u l u i cp d e v i n e a d r e s a v a r i a b i l e i c
6
7
8 p r i n t f ( "%c \n" , c ) ;
9 // a f i s e a z a c a r a c t e r u l a f l a t i n v a r i a b i l a c , a d i c a ’ a ’
10
11 p r i n t f ( "%d\n" , cp )
12 // a f i s e a z a numarul i n t r e g a f l a t i n v a r i a b i l a cp , a d i c a ADRESA l u i c
13
14 p r i n t f ( "%c \n" , ∗ cp ) ;
15 // a f i s e a z a c a r a c t e r u l a f l a t l a a d r e s a d i n cp , a d i c a ’ a ’
16
17 ∗ cp = ’ b ’ ;
18 // s e t r a d u c e p r i n : v a l o a r e a de l a a d r e s a d i n cp d e v i n e ’ b ’
19
20 p r i n t f ( "%c \n" , c ) ; // a f i s e a z a ’b ’

În exemplul de mai sus pointerul cp se foloseşte la liniile 14 şi 17 pentru a accesa


zona de memorie către care pointerul "arată". La linia 14 accesăm spre a citi
acea zonă de memorie, iar la linia 17 pentru a scrie o (altă) valoare în ea.

*cp = ’b’ se traduce prin "valoarea de la adresa din cp devine ’b’". În ge-
neral, cu excepţia declarării pointerului (linia 2), *cp se traduce prin "valoarea
de la adresa din cp". În acest caz, când cp = &c (cp ia valoarea adresei lui c),
*cp reprezintă, pentru operaţii de citire şi/sau scriere acelaşi lucru ca şi c. Fie
că executăm *cp = ’a’ sau c = ’a’, rezultatul este acelaşi.
Notă. ’a’, ’b’, etc sunt ceea ce noi numim caractere. De fapt ele sunt numere
întregi, corelate cu codul ASCII: ’a’ = 97, ’b’ = 98, ’A’ = 65, etc. În exemplul
de mai sus, scrierea c = ’a’ este perfect identică cu c = 97. Amintiţi-vă codul
ASCII.

2
2.1.1 Alocarea dinamică
Alocarea dinamică, spre deosebire de cea statică, presupune că programatorul
nu ştie dinainte exact câtă memorie are nevoie pentru a-şi putea executa pro-
gramul. Un bun exemplu pentru un astfel de caz constituie memorarea unui
nume sub formă de vector de caractere. Lungimea unui nume poate varia de la
câteva caractere până la câteva zeci dacă persoana în cauză are mai multe pre-
nume. Dacă declarăm static un vector de 100 de caractere (presupunând totuşi
că 100 ar fi lungimea maximă), atunci pentru orice nume ţinut minte, ocupăm
100 de octeţi. Acest lucru este foarte ineficient pentru lungimi de nume mici.
În momentul în care memoria disponibilă nu este foarte mare, sau în momentul
în care numărul de nume care trebuiesc memorate este foarte mare, acest lucru
nu mai este fezabil.

Alocarea dinamică presupune memorie disponibilă şi un pointer care să


ţină minte la ce adresă este această memorie disponibilă. Dacă alocarea statică
era facută de către compilator (inclusiv dezalocarea era automată, tot de către
compilator, în cazul alocării dinamice aceasta este iniţiată de către program,
care "cere" memorie sistemului de operare. Foarte important, în C/C++ dez-
alocarea memoriei trebuie făcută neapărat tot de către program! Memorie
nefolosită şi ne-dezalocată înseamnă memorie pierdută.

Alocarea de memorie se face în felul următor (de exemplu, pentru un vector


de char):
1 char ∗ s ;
2 int n = 10;
3 s = new c h a r [ n ] ;

În momentul alocării, pointerului s i se schimbă valoarea în valoarea adresei


zonei de memorie proaspăt alocată. Există totuşi o excepţie: dacă sistemul
de operare nu are memorie disponibilă, sau din orice alt motiv nu poate aloca
memorie, operatorul new va returna valoarea NULL. Din acest motiv, când se
foloseşte alocarea dinamică trebuie neapărat făcută verificarea că memoria s-a
alocat:
1 char ∗ s ;
2 int n = 10;
3 s = new c h a r [ n ] ;
4 i f ( s == NULL)
5 {
6 c o u t << " E r o a r e de a l o c a r e ! \ n" ;
7 exit (1) ;
8 }

Practic, dacă nu am reuşit să fac alocarea de memorie, trebuie să găsesc o


soluţie, deoarece programul meu, pentru a rula în continuare are nevoie de acea
memorie să fie alocată. În cele mai multe cazuri nu există o soluţie, aşa că se va
afişa un mesaj utilizatorului, după care programul se închide (ca şi în exemplul
de mai sus). În exemplele noastre acest lucru nu va fi o problemă.

3
Notă. Atenţie: în momentul alocării, s ţine minte adresa noului spaţiu de
memorie alocat. Dacă cumva s va pierde acea valoare (a adresei spaţiului)
atunci memoria respectivă va fi pierdută dar NU va fi dezalocată automat.
Trebuie avută foarte mare grijă ca să nu fie pierdută adresa spaţiului de memorie
alocat dinamic.

1 char ∗ s ;
2 int n = 4;
3 s = new c h a r [ n ] ;
4 i f ( s == NULL)
5 {
6 c o u t << " E r o a r e de a l o c a r e ! \ n" ;
7 exit (1) ;
8 }
9
10 s [ 0 ] = ’P ’ ;
11 s [ 1 ] = ’C ’ ;
12 s [2] = ’7 ’ ;
13 s [ 3 ] = 0;
14 p r i n t f ( "%s \n" , s ) ; // va a f i s a "PC7"
15
16 s = NULL;
17
18 p r i n t f ( "%s \n" , s ) ; // e r o a r e

În exemplul de mai sus, până la linia 14 inclusiv, programul funcţioneaza corect,


şi este un bun exemplu pentru a arăta folosirea memoriei alocate dinamic. Prac-
tic s se poate folosi exact ca un vector. La linia 16 s pierde valoarea spaţiului
de memorie alocat dinamic, moment în care acel spaţiu nu mai poate fi folosit
(pentru ca nu ştim unde se află). Atenţie: acest lucru nu trebuie să se întample
niciodată.

În exemplul de mai jos este prezentată rutina completă de folosinţă a memoriei


alocate dinamic:

1. Alocare
2. Verificarea dacă s-a alocat
3. Folosirea acelui spaţiu de memorie
4. Eliberarea (sau dezalocarea) spaţiului de memorie când nu mai este folosit

5. Resetarea pointerului la NULL (optional)

1 char ∗ s ;
2 int n = 4;
3 s = new c h a r [ n ] ;
4 i f ( s == NULL)
5 {
6 c o u t << " E r o a r e de a l o c a r e ! \ n" ;
7 exit (1) ;
8 }

4
9
10 s [0] = ’P ’ ; // sau ∗ s = ’P ’ ;
11 s [1] = ’C ’ ; // sau ∗ ( s +1) = ’C ’
12 s [2] = ’7 ’ ; // sau ∗ ( s +2) = ’ 7 ’
13 s [3] = 0; // sau ∗ ( s +3) = 0
14
15 p r i n t f ( "%s \n" , s ) ; // va a f i s a "PC7"
16
17 delete [ ] s ;
18 s = NULL;

Notă. Liniile 10-13 prezintă folosirea memoriei dinamice prin pointer ca vector.
Această scriere este mai uşoară şi mai simplu de urmărit. Totuşi, scrierea
perfect corectă este dată la comentariu. *s înseamnă "valoarea de la adresa din
pointerul s", *(s+1) înseamnă "valoarea de la (adresa din pointerul s, plus o
unitate de adresă)". Această unitate nu reprezintă neapărat un octet, ci exact
numărul de octeţi ai tipului de date pe care-l reprezinta pointerul.

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