Sunteți pe pagina 1din 21

8.

Tablouri i pointeri

Att tablourile, ct i pointerii reprezint tipuri de date derivate. Tablourile sunt derivate din tipul
de date al elementelor componente, iar pointerii sunt derivai din tipul de date al elementelor spre
care indic. n plus, tablourile reprezint tipuri de date compuse, metoda de structurare a
elementelor componente ntr-un tablou fiind realizat cu ajutorul indexrii. n mod uzual studiul
celor dou tipuri de date se face mpreun, deoarece tablourile sunt tratate de ctre
compilatoarele limbajului C ca nite pointeri constani, la care se adaug anumite metode de
acces la elmentele componente.

8.1 Tablouri
Din punct de vedere al tipurilor de date, tipurile tablou sunt tipuri de date compuse, n sensul c
un element de tip tablou este compus din mai multe elemente aparinnd altor tipuri de date,
numite componentele tabloului. Toate componentele unui tablou trebuie s aparin ns
aceluiai tip de date, numit tip de date de baz, ceea ce face ca tablourile s fie n acelai timp i
tipuri de date derivate.
Structurarea elementelor componente ale tablourilor se face cu ajutorul indicilor:
tablourile cu un singur indice se numesc tablouri unidimensionale, cele cu doi indici sunt tablouri
bidimensionale, iar n general tablourile cu mai muli indici sunt tablouri multidimensionale.
Tablourile unidimensionale sunt tipurile fundamentale, deoarece tablourile multidimensionale se
definesc pe baza celor unidimensionale.
Deoarece tablourile sunt tipuri de date derivate, definirea sau declararea acestora se face
prin declararea tipului de baz utiliznd operatorul de derivare ([]). n cazul tablourilor
multidimensionale, operatorul [] se aplic pentru fiecare indice separat. De exemplu, secvena
de program urmtoare:
double x[5], d[3][4], z[2][4][3];

definete tabloul unidimensional x cu 5 componente de tip double, tabloul bidimensional d ca


un tablou unidimensional de 3 componente, fiecare component fiind un tablou unidimansional
de 4 componente de tip double i tabloul tridimensional z ca fiind un tablou unidimensional
de 2 componente care sunt tablouri bidimensionale de 43 componente de tip double.
Observaie. Pentru fiecare indice trebuie specificat numrul de elemente asociat indicelui
respectiv. Aceasta nseamn c n limbajul C nu sunt permise tablouri cu dimensiune variabil,
cum sunt cele din limbajul Basic de exemplu. Alocarea memoriei pentru tablouri este static, n
sensul c dimensiunile tablourilor se stabilesc la compilarea programului i nu n timpul
execuiei acestuia.

Pentru referirea componentelor unui tablou se utilizeaz operatorul de indexare (reprezentat tot
de grupul de caractere[]) conform sintaxei:
<tablou>[<indice>]

unde <indice> reprezint o expresie ntreg. O component a unui tablou reprezint o Lvaloare deoarece are asociat o zon de memorie pentru pstrarea valorii curente.
Observaie. n cazul tablourilor multiplu indexate, operatorul de indexare se specific pentru
fiecare indice. De exemplu, pentru un tablou bidimensional cu numele a, elementul de pe linia i
i coloana j se refer a[i][j], deoarece a[i] reprezint el nsui un tablou unidimensional.
Dup cum s-a precizat anterior, indicii elementelor unui tablou sunt numere ntregi pozitive;
indicele primului element este ntotdeauna zero, pe cnd indicele ultimului element este mai mic
cu 1 dect numrul total de elemente pentru indicele respectiv. Dei compilatoarele limbajului C
nu fac o verificare a valorilor pentru indicii tablourilor (i deci nu genereaz mesaje de eroare la
compilare), utilizarea unui element dint-un tablou care are indicele n afara domeniului corect de
valori poate conduce la erori n execuia unui program.
Modul de alocare a memoriei pentru componentele unui tablou este liniar, indiferent de
numrul de indici al acestuia: elementele tabloului se aloc n ordinea natural a creterii
indicilor. De exemplu, alocarea memoriei pentru un tablou tridimensional definit astfel:
int r[2][3][2];

presupune urmtoarea ordine a componentelor:


r[0][0][0],
r[0][2][1],
r[1][2][0],

r[0][0][1],
r[1][0][0],
r[1][2][1].

r[0][1][0],
r[1][0][1],

r[0][1][1],
r[1][1][0],

r[0][2][0],
r[1][1][1],

Datorit faptului c alocarea memoriei pentru elementele unui tablou se face liniar, n cazul
expresiilor ce conin referire la componentele unui tablou, compilatorul trebuie s genereze
adresele de memorie asociate componentelor respective. Modul de determinarea a adresei
componentelor tablourilor va fi descris ntr-un paragraf ulterior.
Exemplul 8.1. Se consider o mulime finit de elemente X = {x1, x2, , xn}. O relaie de
compunere algebric pe mulimea X este o funcie p de forma:
p: XX X
S se determine dac o relaie algebric dat este comutativ sau nu.
Descrierea algoritmului. Pentru reprezentarea relaiei de compunere algebric se pot utiliza dou
tablouri: un vector X=(xi), i=1,,n pentru memorarea elementelor mulimii X i o matrice
P=(pij), i=1,,n, j=1,,n pentru reprezentarea elementelor funciei p. Deoarece problema cere
s se determine doar dac relaia este comutativ, mrimile de intrare ale problemei vor fi doar
numrul n de elemente ale mulimii X, precum i matricea P. n aceste condiii, relaia de
compunere p este comutativ dac matricea asociat P este simetric. Se consider c mulimea
X are ca elemente numere reale.
Descrierea programului.
#include <stdio.h>

int main(void) {
int i, j, n, comutativ=1;
float p[20][20];
printf(\nIntroduceti n: );
scanf(%d, &n);
printf(\nIntroduceti elementele relatiei: );
for(i=0; i<n; i++)
for(j=0; j<n; j++) {
printf(\np[%d][%d]= , i, j);
scanf(%f, &p[i][j]);
}
for(i=0; i<n; i++)
for(j=0; j<n; j++)
if(p[i][j] != p[j][i])
comutativ = 0;
if(comutativ)
printf(\nRelatia este comutativa);
else
printf(\nRelatia nu este comutativa);
return 0;
}

Observaie. Din programul anterior se observ faptul tabloul p a fost declarat cu 20 de linii i 20
de coloane, fr s se cunoasc efectiv numrul real de componente al matricii, memorat de
variabila n (acest lucru se poate determina doar n timpul execuiei programului, cnd variabila n
va avea o valoare citit de la terminalul de intrare). ntruct limbajul C impune declararea
tablourilor cu un numr constant de componente, trebuie ca pentru tablourile pentru care nu se
cunosc dimensiunile n etapa de elaborare a programelor, s se declare o dimensiune maxim
suficient pentru fiecare indice.
Un tablou poate fi definit sau declarat incomplet, fr s se specifice dimensiunea sa, dar
asemenea situaii sunt puine, deoarece fr specificarea dimensiunii nu se poate realiza alocarea
de memorie pentru elementele tabloului. Cazurile uzuale n care se poate omite specificarea
dimensiunii unui tablou sunt urmtoarele :
a. declaraia se refer la un tablou extern, pentru care s-a alocat memorie n alt fiier surs ;
b. definiia conine o iniializare, pe baza creia se poate determina dimensiunea tabloului ;
c. tabloul este declarat ca parametru formal ntr-o funcie, caz n care declaraia este
echivalent cu cea a unui pointer.
Observaie. n cazul tablourilor multidimensionale, numai prima dimensiune poate rmne
neprecizat, n caz contrar nu se pot determina adresele de memorie a elementelor componente.
De exemplu, n secvena urmtoar se definesc trei tablouri, a, b i c, dar numai prima definiie
este corect.
int a[][2][3] ;
int b[][][3] ;
int c[][][] ;

Iniializarea tablourilor este o operaie opional i poate nsoi definiia acestora. Specificarea
valorii iniiale a elementelor unui tablou se realizeaz prin specificarea listei valorilor

elementelor componente, separate prin virgule i nchise ntre acolade. Expresiile folosite pentru
iniializare trebuie s fie expresii constante, care se pot evalua la compilare.
Exemple.
int v[6] = {1, 2, 1, 2, 1, 2} ;
double a[2][3] = { {0, 0, 0}, {1, 1, 1} } ;
double b[2][3] = { 0, 0, 0, 1, 1, 1 } ;
float x[] = {1, 1, 1, 1} ;
char t1[] = "Exemplu" ;
char t2[8] = {E, x, e, m, p, l, u, \0} ;

n exemplul anterior, tabloul x are patru elemente, dimensiune determinat din numrul de
elemente iniializate.
Observaii.
1. n cazul tablourilor multidimensionale, fiecare element compus se specific ntre acolade.
Acest lucru nu este obligatoriu, dac elementele componente se specific n ordinea de
stocare n memorie. n exemplul precedent, tablourile a i b au aceeai valoare iniial.
2. n cazul tablourilor de caractere, iniializarea se poate efectua fie prin specificarea fiecrui
caracter ca element de tablou (cazul tabloului t2), fie prin specificarea irului de caractere
drept o constant de tip ir de caractere (cazul tabloului t1) . n al doilea caz se adaug la
sfrit caracterul cu codul zero. n exemplul precedent, tablourile t1 i t2 au aceeai
iniializare.

8.2 Pointeri i adrese de memorie


Tipurile pointer reprezint, alturi de tipurile tablou, cele mai utilizate tipuri derivate ale
limbajului C. Mai mult, pointerii confer limbajului o deosebit for i flexibilate.
Obiectele majoritii tipurilor de date sunt elemente statice, n sensul urmtor: n
momentul declarrii unei variabile, acesteia i se aloc o zon liber de memorie unde variabila i
pstreaz valoarea n timpul execuiei programului, iar perechea (<nume variabil> , <zon de
memorie>) rmne aceeai n tot timpul execuiei programului. Pointerii permit utilizarea
obiectelor dinamice, permind utilizatorilor crearea i distrugerea n mod explicit a acestora.
Crearea unui obiect nseamn de fapt alocarea unei zone de memorie pentru obiectul respectiv,
iar distrugerea obiectului nseamn dealocarea zonei de memorie aferente.
Un pointer este o variabil care conine adresa de memorie a unui obiect (indicnd astfel
spre obiectul respectiv), permind accesarea indirect a obiectului. ntruct exist moduri
diferite de reprezentare intern pentru diferite tipuri de date, declararea unei variabile pointer
trebuie s indice tipul de date al obiectelor spre care aceasta indic.
Exemplu. Se definete variabila pointer pn care va indica spre obiecte de tip ntreg i variabila
px care va indica spre obiecte de tip real.
int *pn;
float *px;

Definirea pointerilor utilizeaz operatorul de derivare *, care se deosebete (din context) de


operatorul de nmulire. n definiia anterioar, int* reprezint tipul de date pointer spre tipul
ntreg, adic mulimea elementelor care indic spre obiecte de tip ntreg, iar int *pn;
specific faptul c pn indic spre un obiect de tip int. n general, dac tip reprezint un tip
de date, atunci notaia tip* reprezint tipul de date pointer care indic spre elementele tipului
tip (adui mulimea elementelor ce indic spre obiecte ale tipului tip).
Observaie. n cadrul unei definiii aferent unui pointer, prezena spaiului este nesemnificativ.
De exemplu, urmtoarele definiii sunt echivalente:
int *pn;
int * pn;
int* pn;
int*pn;

Definiia pointerilor se poate face mpreun cu definirea altor elemente aparinnd tipului de date
spre care pointerii indic. De exemplu, declaraia:
int *p, k, m;

definete variabila p ca fiind un pointer ca indic spre obiecte de tipul int, iar variabilele k i m
ca fiind de tipul int.
Pentru a atribui unui pointer adresa de memorie a unei variabile, se poate utiliza operatorul de
adresare &, ca n exemplul urmtor:
p = &k;

ceea ce nsemn c variabila p indic spre obiectul k.


Accesarea obiectului referit de un pointer se face prin intermediul operatorului de dereferire (se
utilizeaz tot caracterul *), ca n urmtorul exemplu:
m = *p;

Operatorul de dereferire se deosebete din context de operatorii de nmulire i de derivare


reprezentai de acelai caracter.
Observaie. n cadrul unor expresii, p reprezint adresa de memorie a unui obiect, iar *p
reprezint coninutul zonei de memorie respective (obiectul nsui).
Crearea i distrugerea obiectelor dinamice se realizeaz prin intermediul unor funcii standard.
Declaraiile referitoare la acestea se afl n fiierul standard alloc.h. Cele mai utilizate funcii
sunt: malloc pentru alocarea unei zone de memorie i free pentru dealocarea unei zone de
memorie alocate anterior. Memoria pentru obiectele dinamice se aloc ntotdeauna n zona heap
a programului.
Funcia free are un singur parametru reprezentat de o variabil pointer, efecul ei
constnd n eliberarea zonei de memorie aferent pointerului specificat ca parametru. Prototipul
funciei este:
void free(void* <pointer>);

Funcia malloc are un parametru ntreg reprezentnd numarul de octei de memorie ce se


doresc a fi alocai, efectul ei constnd n alocarea unei zone de memorie libere de o dimensiune

specificat i returnarea adresei de nceput a acesteia. n cazul acestei funcii apare o problem
referitoare la tipul de date al obiectului care se va reprezenta n respectiva zon de memorie.
ntruct funcia malloc va fi folosit pentru crearea obiectelor dinamice de diverse tipuri,
rezultatul returnat de funcie este de tipul void*. n acest fel, programatorul trebuie s modifice
tipul de date rezultat n funcie de tipul obiectelor dinamice create, folosind un operator de
conversie de tip. Prototipul funciei este:
void* malloc(size_t <numar_octeti>);

Observaie. size_t reprezint o redefinire a tpului ntreg fr semn, fiind tipul rezultatului
returnat de operatorul sizeof.
Exemple. n urmtoarea secven de program se definete un pointer la un tip ntreg, se creaz
un obiect dinamic (indicat de pointer), se iniializeaz cu o valoare i apoi se distruge obiectul
creat.
int *p;
p = (int*)malloc(sizeof(int));
*p = 3;
free(p);

Rezultatul returnat de funcia malloc este convertit la tipul int* pentru a fi compatibil cu
poinerul p. Parametrul fuciei malloc trebuie s indice numrul de octei utilizai pentru
reprezentatrea obiectelor de tip int. ntruct reprezentarea obiectelor tipului int este
dependent de calculator, se folosete operatorul sizeof, care returneaz numrul de octei
ocupat de tipul de date al parametrului.
n concluzie, dac T reprezint un tip de date i pointerul p este definit ca:
T *p;

atunci alocarea memoriei pentru un obiect dinamic de tipul T se realizeaz n mod uzual astfel:
p = (T*)malloc(sizeof(T));

Observaie. Un apel de forma: free(p); nu se poate face dect dac anterior s-a alocat
memorie pentru pointerul p, altfel execuia programului va fi eronat.
n afar de malloc, se mai pot utiliza i alte funcii de alocare a memoriei. Iat principalele
funcii:
1) Funcia calloc, aloc n blocuri consecutive de memorie i returneaz adresa de nceput a
zonei alocate, sau NULL n cazul n care nu se poate aloca memorie. n plus, ea iniializeaz
zona de memorie alocat cu zero.
void* calloc(size_t <nr_blocuri>, size_t <dim_bloc>);

2) Funcia ralloc ncearc s realoce memoria pentru un anumit bloc de memorie,


actualiznd mrimea blocului, dac este posibil. n acest caz trebuie specificat adresa
blocului alocat anterior i noua mrime a acestuia. n cazul n care nu exist memorie
suficient pentru noua mrime a blocului, acesta rmne nemodificat, altfel, zona de
memorie se extinde pn la mrimea specificat. Funcia returneaz adresa de memorie a
blocului realocat, sau NULL dac nu s-a putut realoca memorie. La primul apel al acestei

funcii, adresa blocului de memorie alocat anterior este NULL i funcia realloc se
comport identic funciei malloc.
void* realloc(void* <adresa_bloc>, size_t <dim_bloc>);

Exemplul 8.2. Urmtorul program are doar un scop didactic i folosete obiecte dinamice pentru
determinarea mediei aritmetice a trei numere reale.
#include <stdio.h>
#include <alloc.h>
int main(void) {
float *pmed, *px1, *px2, *px3;
px1 = (float *)malloc(sizeof(float);
px2 = (float *)malloc(sizeof(float);
px3 = (float *)malloc(sizeof(float);
pmed = (float *)malloc(sizeof(float);
printf(\nIntroduceti x1, x2, x3: );
scanf(%f%f%f, px1, px2, px3);
*pmed = (*px1 + *px2 + *px3)/3;
printf(\nmed=%f, *pmed);
return 0;
}

Observaii.
1. ntruct variabilele pointer (px1, px2 i px3 n exemplul anterior) reprezint chiar adrese se
memorie, ele nu mai sunt prefixate de operatorul de adresare & in cadrul funciei scanf.
2. Distrugerea obiectelor dianmice se face cu ajutorul funciei free, iar dac aceasta nu apare,
distrugerea se realizeaz automat la sfritul execuiei programului, cum este cazul
exemplului precedent.

8.3 Pointeri i tablouri. Aritmetica pointerilor


Spre deosebire de alte limbaje de programare, limbajul C permite efectuarea anumitor operaii
aritmetice asupra pointerilor, operaii permise n general n cadrul limbajelor de asamblare.
Aceste operaii evideniaz strsa legtur care exist ntre pointeri i tablouri.
Principalii operatori aritmetici ce pot fi utilizai n cazul pointerilor reprezint operaiile
aditive: +, -, ++, --. Toi aceti operatori necesit cel puin un operand de tip pointer.
Operaiile de adunare i scdere se pot defini ntre pointeri sau ntre pointeri i numere
ntregi:
- n cazul adunrii operandul din stnga este obligatoriu un pointer, operandul din
dreapta este un ntreg, iar rezultatul reprezint tot un pointer de aclai tip cu
operandul stng. Dac, de exemplu, T reprezint un tip de date i n este o expresie
ntrag, iar un poiner p este definit ca:
T *p;

Atunci rezultatul evalurii expresiei:


p+n

este un pointer de tipul T a crui valoare este egal cu valoarea lui p, la care se adaug
valoarea:
n*sizeof(T)

Cu alte cuvinte, p+n reprezint adresa celui de-al n-lea obiect consecutiv dup cel
indicat de p.
Observaie. n cazul adunrii, nu se pot aduna doi pointeri, chiar dac sunt de acelai
tip.
n cazul scderii operandul din stnga este obligatoriu un pointer, iar operandul din
dreapta poate fi un pointer de aselai tip, sau un ntreg:
o n cazul n care operandul al doilea este un ntreg, rezultatul reprezint tot un
pointer de aclai tip cu operandul stng. Dac T reprezint un tip de date i n
este o expresie ntrag, iar un poiner p este definit ca:
T *p;

atunci rezultatul evalurii expresiei:


p-n

este un pointer de tipul T a crui valoare este egal cu valoarea lui p, la care se
scade valoarea:
n*sizeof(T)

Cu alte cuvinte,
p-n
reprezint adresa celui de-al n-lea obiect naintea celui indicat de p.
o n cazul n care operandul al doilea este un pointer, de acelai tip cu tipul
primului operant, rezultatul este un ntreg ce reprezint numrul de obiecte de
acelai tip dintre adresele indicate de cei doi pointeri. S considerm
urmtoarea secven de cod:
int v[10] = {0};
int i = 4, j = 2, n;
int* p = &v[i];
int* q = &v[j];
n = p q;

atunci:
Eval(p-q) = Eval(i-j) = 2

Legtura ntre tablouri i pointeri se poate observa simplu din urmtorul exemplu. Dac se
consider declaraiile:
int v[] = {1, 2, 3};
int* p = &v[0];
int n = 1;

atunci expresia v[n] este echivalent cu *(p+n), iar expresia &v[n] este echivalent cu
p+n. Cu alte cuvinte, tablourile sunt considerate de ctre compilator ca nite pointeri constani:
variabila v din definiia anterioar este considerat ca un pointer de tipul const int*, a crei
valoare este iniializat la adresa de memorie a primului element al tabloului, iar aceast valoare
nu poate fi modificat n timpul execuiei programului.
n mod asemntor se poate defini operaia de scdere: p-n poate fi interpretat ca fiind
expresia p+(-n) i reprezint adresa celui de-al n-lea element anterior celui indicat de p.

Observaie. n cazul tablourilor se pot ns referi elemente aflate n afara zonai de memorie
alocate, ceea ce impune o atenie n ceea ce privete valoarea indicelui unui element al tabloului
(compilatorul nu face o verificare a depirii zonei de memorie ataat tabloului). De exemplu,
considernd un tablou a cu 15 componente de tip int i n o variabil de tip int, atunci expresia
&a[-n] este echivalent cu a-n, adic este egal cu valoarea:
a n * sizeof(int)

ceea ce reprezint o adres de memorie corect pentru compilator. n mod asemntor, expresia
&a[n] este corect chiar dac n depete numrul maxim de componente al tabloului (15),
deoarece valoarea:
a + n * sizeof(int)

reprezint o adres de memorie corect.


Se poate acum descrie modul de determinare a adresei de memorie pentru un element dintr-un
tablou. n cazul tablourilor unidimensionale, referirea unei componente a[i] se face simplu,
compilatorul genernd expresia echivalent:
*(&a[0] + i)

ceea ce din punctul de vedere al pointerilor nseamn *(a+i).


n cazul tablourilor multidimensionale, va trebui determinat numrul de elemente consecutive
aflate ntre primul element al tabloului i elementul curent ce se dorete s fie referit.
S considerm cazul tablourilor bidimensionale, n care un tablou a are n linii i m coloane:
const int n = 2;
const int m = 3;

S presupunem c se dorete referirea elementului a[i][j]. Notnd cu k numrul de elemente


consecutive din tablou aflate ntre primul element a[0][0] i elementul curent, expresia
echivalent generat de compilator pentru a[i][j]este:
*(&a[0][0] + k)

Relaia ntre i, j i k se poate determina simplu, dac se numr toate elementele aflate pe liniile
0, 1, , i-1, la care se adaug cele j elemente de pe linia i. Deoarece toate liniile au m elemente,
se poate scrie:
k = m*i+j

Rezult c formula generat de compilator la evaluarea expresiei a[i][j]este:


(&a[0][0] + m*i + j)

Observaie. n relaia anterioar nu apare numrul maxim de linii al tabloului a. Din acest motiv,
exist situaii n care compilatorul permite specificarea incomplet a tablourilor.
Formula de acces pentru componentele unui tablou tridimensional, sau n cazul general kdimensional se poate deduce n mod asemntor.
Expresiile ce conin pointeri i operatori de incrementare/decrementare se interpreteaz n
acelai mod: ei permit indicarea spre elementul succesor/predecsor al elementului curent indicat
de pointerul p. n acest caz ns pot apare confuzii legate de ordinea de evaluare a celor doi
operatori (operatorul de dereferire i cel de incrementare/decrementare). Trebuie reinut faptul c
operatorii de incrementare/decrementare sunt mai prioritari dect operatorul de dereferire.

Urmtorul tabel conine principalele expresii cu aceti operatori combinai (s-a specificat n
tablou doar operatorul de incrementare):
*p++
*++p
(*p)++
++*p

Se aplic nti operatorul de dereferire (se evalueaz obiectul referit de


p, care este i valoarea de evaluare a expresiei) i apoi se aplic
operatorul de incrementare (se incrementeaz pointerul)
Se incrementeaz nti valoarea pointerului i apoi se aplic operatorul
de dereferire (se evalueaz obiectul referit de acesta)
Se aplic nti operatorul de dereferire (se evalueaz obiectul referit de
p) i apoi cel de incrementare (se incrementeaz valoarea acestui obiect)
Se aplic nt operatorul de incrementare (se incrementeaz obiectul
referit de p) i apoi cel de dereferire (valoarea rezultat n urma
incrementrii fiind i valoarea de evaluare a expresiei)

Din tabelul precedent se observ faptul c primele dou expresii acioneaz asupra pointerului,
pe cnd ultimele doua supra obiectului indicat de acesta.
n mod asemntor se pot descrie expresiile combinate ce conin operatori de
decrementare i de dereferire.
n cazul n care ambii operanzi dint-o expresie sunt pointeri, singura operaie permis este
cea de scdere. n acest caz, ambii pointerii trebuie sa aib acelai tip de baz. Dac p1 i p2 sunt
pointeri la un tip T, atunci valoarea expresiei p1-p2 este un ntreg ce cpecific numrul de
obiecte de tipul T aflate ntre obiectele indicate de p1 i p2.
Pe baza operaiei de scdere a pointerilor se definesc operatorii de relaie aplicai pointerilor,
conform tabelului urmtor (p1 i p2 sunt pointeri asociai aceluia tip de date de baz):
Expresie
p1 < p2
p1 <= p2
p1 > p2
p1 >= p2

Semnificaie
p1 p2 < 0
p1 p2 <= 0
p1 p2 > 0
p1 p2 >= 0

n cazul tablourilor multidimensionale se pot scrie expresii mai mult sau mai puin complexe n
care apar operatori de indexare i de dereferire. Semnificaia evalurii unei asemenea expresii se
poate determina relativ simplu dac se ine seama de precedena operatorilor. Tabelul urmtor
prezint cteva exemple de utilizare a acestor operatori n cazul unui tablou bidimensional,
considernd declaraia urmtoare:
int a[3][4];
Expresie
a
*a
**a
a[1]
*a[1]

Semnificaie
Pointer la un vector de 4 elemente int (prima linie a matricii)
Pointer la un element int (prima linie, prima coloan)
Un element int (prima linie, prima coloan)
Pointer la un element element int (linia a doua, prima coloan)
Un element element int (linia a doua, prima coloan)

a+1
*a+1
a[1][1]
a[1]+1

Pointer la un vector de 4 elemente int (linia a doua)


Pointer la un element int (prima linie, a doua coloan)
Un element int (linia a doua, coloana a doua)
Pointer la un element element int (linia a doua, coloana a doua)

n general, pentru a nelege o referin la un tablou multidimensional trebuie avute n vedere


urmtoarele observaii:
specificarea complet a unei referine la un tablou reprezint un element al tabloului;
specificarea incomplet a unei referine se substituie cu un pointer;
un nivel de indirectare este echivalent cu un pointer.

8.4 Alocarea dinamic a tablourilor


Unul dintre principalele dezavantaje ale utilizrii tablourilor este acela c ofer o alocare static a
elementelor componente, n sensul c pentru fiecare tablou definit ntr-un program se aloc un
numr fix de componente stabilit n etapa de compilare, indiferent de numrul real de
componente folosit n timpul execuiei programului. Mecanismul pointerilor permite utilizarea
tablourilor dinamice, pentru care alocarea memoriei se face n timpul execuiei programelor, dar
n acest caz alocarea trebuie realizat n mod explicit de ctre programator.
n cazul alocrii dinamice a unui tablou unidimensional, se poate folosi o variabil de tip
pointer care indic spre primul element din tablou. De exemplu, pentru un tabou de numere
ntregi, pointerul se declar astfel:
int *pv;

Alocarea memoriei pentru n elemente de tip int se poate realiza cu ajutorul funciei malloc, ca
n figura 8.1.
pv = (int*)malloc(n*sizeof(int));

pv
0

n-1

Figura 8.1. Alocarea memoriei pentru o valore de tip int


Referirea unui element dintr-un asemenea tablou alocat dinamic se poate face utiliznd
operatorul de indexare:
pv[i]

ceea ce este tradus de ctre compilator ca:


*(pv+i)

Dezalocarea memoriei pentru un tablou alocat dinamic se poate realiza simplu cu ajutorul
funciei free:
free(pv);

n cazul tablourilor bidimensionale, o metod simpl este aceea n care o asemenea matrice este
privit ca un vector de pointeri, fiecare element din vector indicnd la o linie a matricii. Pentru
aceasta, se va utiliza un tablou unidimensional asociat liniilor, fiecare element al tabloului
memornd adresa primului element din linia respectiv. De exemplu, pentru un tablou
bidimensional ale crui componente sunt de tip int, declararea se poate face astfel:
int **pa;

iar sugestiv, alocarea memoriei arat ca n figura 8.2.


pa
pa[0]
pa[1]

pa[n]
Figura 8.2. Alocarea memoriei pentru un tablou bidimensional
Alocarea memoriei se face separat pentru vectorul de pointeri la linii, ct i pentru fiecare linie a
tabloului. De exemplu, dac se dorete alocarea unui tablou cu n linii i m coloane, o funcie de
alocare dinamic se poate scrie astfel:
int** AlocaMatrice(int n, int m) {
int k;
int **pa = (int**)malloc(n*sizeof(int*));
for(k=0; k<n; k++)
pa[k] = (int*)malloc(m*sizeof(int));
return pa;
}

Funcia returneaz adresa primului elemnt al vectorului de pointeri la linii. Tipul de date al
tabloului de pointeri la linii este int*, pe cnd al elementelor de pe fiecare linie este int.
Referirea unui element al unui tablou bidimensional cu alocare dinamic se poate face cu
ajutorul operatorului de indexare, ca i n cazul tablourilor cu alocare static. De exemplu, pentru
tabloul declarat int **pa; elementul de pe linia i i coloana j se specific pa[i][j]. Exist
totui o deosebire ntre codul generat de compilator la referirea unei componente dintr+un tablou
cu alocare dinamic i unul cu alocare static.
De exemplu, pentru tabloul definit astfel:
int a[2][3];

referirea elementului a[i][j] se traduce de compilator prin:


*(&a[0][0]+3*i+j)

pe cnd n cazul tabloului definit astfel:


int **pa = AlocaMatrice(2, 3);

referirea elementului pa[i][j] se traduce simplu prin:


*(*(pa+i)+j)

Pentru eliberarea memoriei, trebuie dezalocat att zona de memorie asociat elementelor
liniilor, ct i cea asociat pointerilor la linii:
void DealocaMatrice(int **pa, int n) {
int k;
for(k=0; k<n; k++)
free(pa[k]);
free(pa);
}

Observaie. Metoda utilizat la alocarea dinamic a tablourilor bidimensionale poate fie extins
i la tablourile multidimensionale.
Exemplul 8.3. Se va relua exemplul produsului a dou matrici, utiliznd alocarea dinamic a
acestora.
#include <stdio.h>
#include <alloc.h>
double** AlocaMatrice(int n, int m) {
int k;
double **a = (double**)malloc(n*sizeof(double*));
for(k=0; k<n; k++)
a[k] = (double*)malloc(m*sizeof(double));
return a;
}
void DealocaMatrice(double **a, int n) {
int k;
for(k=0; k<n; k++)
free(a[k]);
free(a);
}
double** CitesteMatrice(int n, int m) {
int i, j;
double **a = AlocaMatrice(n, m);
printf(\nIntroduceti elementele matricii:);
for(i=0; i<n; i++)
for(j=0; j<m; j++)
scanf(%lf, &a[i][j]);
return a;
}
void AfiseazaMatrice(int n, int m) {
int i, j;
printf(\nElementele matricii sunt:);
for(i=0; i<n; i++) {

for(j=0; j<m; j++)


printf(%lf, a[i][j]);
printf(\n);
}
}
double** ProdusMatrici(int n, int p, int m,
double **a, double **b) {
int i, j, k;
double **c = AlocaMatrice(n, m);
for(i=0; i<n; i++)
for(j=0; j<m; j++) {
c[i][j] = 0;
for(k=0; k<p; p++)
c[i][j] += a[i][k]*b[k][j];
}
return c;
}
int main(void) {
int n = 3;
double **a, **b, **c;
a = CitesteMatrice(n, n);
b = CitesteMatrice(n, n);
c = ProdusMatrici(n, n, n, a, b);
AfiseazaMatrice(n, n, c);
DealocaMatrice(a, n);
DealocaMatrice(b, n);
DealocaMatrice(c, n);
return 0;
}

8.5 Pointeri i iruri de caractere


Dei irurile de caractere nu reprezint un tip de date distinct n limbajul C, exist constante de
tip ir de caractere, precum i specificatori de format pentru citirea i scrierea acestor
constante. Aceste faciliti se datoreaz faptului c irurile de caractere sunt des utilizate n
programe, constituind un mijloc puternic de manipulare a informaiei nenumerice.
Un ir de caractere se reprezint ca un tablou de caractere, ultimul caracter fiind
caracterul cu codul zero (\0). Acest caracter joac rolul de terminator al irului, folosirea lui
fiind deosebit de util pentru determinarea sfritului unui ir de caractere.
n cazul tablourilor de caractere exist un mod suplimentar de iniializare, prin care
tabloului i se pate atribui irul de caractere, la care compilatorul adauga n mod automat
caracterul terminator. De exemplu, urmtoarele dou definiii sunt echivalente:
char sir[] = Un sir;
char sir[] = {U, n, , s, i, r, \0};

O constant ir de caractere este interpretat de ctre compilator ca un pointer la tipul char, care
ca valoare adresa de memorie a primului caracter din ir. De exemplu, definiia urmtoare:

char *ps = Alt sir;


este tratat de compilator astfel: se rezerv n memorie 8 octei consecutivi n care se scriu
caracterele: A, l, t, , s, i, r, \0, iar adresa primului octet
se atribuie pointerului ps ca valoare de iniializare. n concluzie, pentru un ir de n caractere,
compilatorul reuerv n+1 locaii de memorie, n locaia n+1 fiind scris caracterul \0.
Observaii.
1. Constantele ir de caractere se pot utiliza n locul pointerilor la caractere, pentru c la
evaluarea unei astfel de constante se returneaz adresa de memorie a primului caracter din ir
(care are tipul char*). De exemplu, expresia abcdefgh+2 are ca valoare adresa de
memorie a caracterului c, iar valoarea expresiei *(abcdefgh+3) este caracterul
d.
2. Ca o consecin a observaiei anterioare, rezult c o constant ir de caractere poate fi
specificat n locul unui nume de tablou de caractere cnd se folosete operatorul de adresare.
De exemplu, expresia abcdefgh[3] este corect i are ca valoare de evaluare tot
caracterul d.
Pentru citirea i scrierea irurilor de caractere se poate utiliza specificatorul de format %s. n
acest caz este indicat utilizarea unei variabile de tip tablou de caractere i nu un pointer la
caractere, deoarece n ultimul caz compilatorul nu rezerv memorie dect pentru pointer i este
posibil s nu se mai detecteze caracterul de sfrit al irului. n exemplul urmtor, al doilea apel
al funciei scanf poate conduce la rezultate eronate:
char s[10], *ps;
scanf(%s, s);
scanf(%s, ps);

Pentru a suplini faptul c limbajul C nu posed un tip de date predefinit pentru irurile de
caractere i nu ofer operatori specifici, sunt puse la dispozie numeroase funcii de bibliotec
pentru prelucrarea acestora. n continuare sunt prezentate cteva funcii mai des utilizate, a cror
declarare de afl n fiierele header string.h i stdio.h.
A) Funcia gets citete un ir de caractere de la terminalul standard de intrare:
char* gets(char* <destinatie>);

irul citit se termin la prima apariie a caracterului \n i va fi scris la adresa indicat de


<destinatie>, dar fr caracterul \n (dar cu caracterul \0 scris la sfritul irului).
B) Funcia puts scrie un ir la terminalul standard de ieire:
int puts(const char* <sursa>);

Ea returneaz ultimul caracter scris sau EOF n caz de eroare. Aceste dou funcii sunt declarate
n fiierul stdio.h.
C) Funcia strcpy este utilizat pentru copierea unui ir de caractere:
char* strcpy(char* <destinatie>, const char* <sursa>);

Ea copiaz irul de caractere (inclusiv terminatorul de ir) din zona de memorie indicat de
pointerul <sursa> n zona de memorie indicat de pointerul <destinatie>.

Pentru o mai bun nelegere a modului de operare cu iruri de caractere, n continuare se


prezint cteva variante ale unei funcii numite MyStrcpy, care realizeaz aceeai operaie ca i
strcpy.
Exemplul 8.4.
char* MyStrcpy1(char* destin, const char* sursa) {
char *temp = destin;
while((*destin = *sursa) != \0) {
destin++;
sursa++;
}
return temp;
}
char* MyStrcpy2(char* destin, const char* sursa) {
char *temp = destin;
while((*destin++ = *sursa++) != \0);
return temp;
}
char* MyStrcpy3(char* destin, const char* sursa) {
char *temp = destin;
while(*destin++ = *sursa++);
return temp;
}

D) Funcia strcat concateneaz dou iruri de caractere:


char* strcpy(char* <sir_1>, char* <sir_2>);

Funcia adaug o copie a irulului <sir_1> la sfritul irului <sir_2> i returneaz irul
rezultat.
E) Funcia strlen determin lungimea unui ir, returnnd numrul de caractere din ir,
exclusiv terminatorul:
size_t strlen(const char* <sir>);

F) Funcia strcmp compar lexicografic dou iruri dou iruri:


int strcpy(const unsigned char* <sir_1>,
const unsigned char char* <sir_2>);

Valoarea returnat este zero dac cele dou iruri sunt egale, un numr negativ dac primul ir
este mai mic (n odrine lexicografic) dect cel de-al doilea i un numr pozitiv dac primul ir
este mai mare dect al doilea.
G) Funcia strchr caut prima apariie a unui caracter ntr-un ir:
char* strchr(const char* <sir>, int <c>);

Funcia returneaz adresa primei aparitii a caracterului n ir sau NULL dac irul nu conine
caracterul cutat.

H) Funcia strrchr realizeaz tot o cutare a unui caracter ntr-un ir, dar n sens invers:
char* strrchr(const char* <sir>, int <c>);

Funcia returneaz adresa ultimei aparitii a caracterului n ir sau NULL dac irul nu conine
caracterul cutat.
I) Funcia strstr caut un subir de caractere ntr-un ir:
char* strstr(const char* <sir>, const char* <subsir>);

Funcia caut irul de caractere n irul specificat i returneaz un pointer la primul caracter din
prima apariie a subirului n ir; dac subirul nu a fost gsit se returneaz NULL.
Un alg grup de funcii se refer la conversia irurilor de caractere n valori numerice. De
exemplu, irul s = 123 poate fi convertit n numrul ntreg 123 sau n numrul real
corespunztor. n mod uzual declaraiile acestor funcii se afl n fiierul stdlib.h.
funcia atoi convertete un ir la o valoare ntreg:
int atoi(const char* <sir>);

funcia atol convertete un ir la o valoare ntreg de tip long:


long atol(const char* <sir>);

funcia atof convertete un ir la o valoare real de tip double:


double atof(const char* <sir>);

Pentru exemplificare se va rezolva o problem de prelucrare a irurilor de caractere.


Exemplul 8.5. De la terminalul standard de intrare se introduce un text de maxim 256 caractere.
Pentru simplitate, se presupune ca textul se termin la apsarea tastei ENTER i este format din
propozii terminate cu caracterul punct, iar propoziiile conin cuvinte separate de un spaiu. S
se afieze pe ecran textul pe linii, considernd c fiecare linie are cel mult N=82 caractere.
Cuvintele nu se vor despri pe dou linii, cu execpia celor ce au mai mult de N caractere.
Descrierea algoritmului. Se vor utiliza dou tablouri de caractere, unul pentru citirea textului,
notat text, iar al doilea pentru afiarea unei linii curente, notat linie. Din irul text se vor extrage
pe rnd subirurile ce formeaz o linie curent de afiare.
Pentru determinarea subirului ce formeaz o line de afiare se vor utiliza trei pointeri, p,
p1 i p2 ca n figura 8.3, cu urmtoarea semnificaie: p indic spre caracterul din text de la care
ncepe linia curent de afiare, p1 indic spre un caracter spaiu ntlnit n text, iar p2 spre primul
spaiu dup p1. Un cuvnt din text curent din text este ncadrat ntre p1 i p2.
text
p

p1

p2

Figura 8.3. Utilizarea pointerilor pentru specificarea unui subir


Cu ajutorul pointerilor p1 i p2 se va determina primul cuvnt din text care nu mai ncape pe
linia curent, adic acel cuvnt pentru care poz(p1)N i poz(p2)N. Am notat cu
poz(pi) este poziia relativ a caracterului indicat de pi la caracterul indicat de p (adic pi-p)
Structura algoritmului este urmtoarea:

start
citeste text
p &text[0]
p2 p
cat timp pNULL executa

p1 p2

p2 strstr(p, )

daca (p2NULL) (p2-p>N) atunci

daca p2-p1>N atunci

*scrie din text in linie N caractere

incepand de la de la pointerul p *

p p+N+1

altfel

*scrie din text in linie caracterele

de la pointerul p la pointerul p1*

p p1+1

p2 p

scrie linie

altfel

daca p2=NULL atunci

*scrie din text in linie caracterele

ramase*

scrie linie

p NULL

sfarsit

Descrierea programului.
#include <stdio.h>
#include <string.h>
#define L_TEXT 256
#define L_LINIE 82
int main() {
char text[L_TEXT], linie[L_LINIE], spatiu[] = ;
char *p, *p1, *p2, *r;
int N, k;
printf(\nNumarul maxim de caractere pe linie: );
scanf(%d, &N);
getchar();
/* sare peste caracterul ENTER */
printf(\nIntroduceti textul:\n);
gets(text);
printf("\nTextul formatat este:\n");

p = text;
p2 = p;
while (p) {
p1 = p2;
p2 = strstr(p1, spatiu)+1;
if (p2 && p2-p>N) {
if (p2-p1>N) {
r = linie;
k = 0;
while (k++<N)
*r++ = *p++;
*r = '\0';
}
else {
r = linie;
while (p<p1)
*r++ = *p++;
*r = '\0';
}
p2 = p;
puts(linie);
}
else if (p2 == NULL) {
r = linie;
while (p)
*r++ = *p++;
*r = '\0';
puts(linie);
}
}
return 0;
}

Observaie. n afar de tipul char, n standardul limbajului C mai este definit un tip de date
numit wchar_t (abreviere pentru wide char type), reprezentnd caractere pe doi octei.
Tipul wchar_t se utilizeaz n cazul altor alfabete dect cel englez, cnd se folosete setul
extins de caractere. n general, toate funciile care lucreaz cu caractere de tip char sunt dublate
de funcii care lucreaz cu wchar_t. Iat cteva exemple:
strcat
strcpy
strlen
strchr
strrchr
strstr

wcscat
wcscpy
wcslen
wcschr
wcsrchr
wcsstr

Funciile care lucreaz cu wchar_t, au o sintax asemntoare cu celelalte, ceea ce difer fiind
tipul parametrilor (wchar_t n loc de char).

8.6 Probleme

8.1.

S se scrie formula de acces pentru componentele unui tablou tridimensional i n cazul


general pentru un tablou k-dimensional, n mod asemntor celei prezentate n paragraful
8.3.

8.2.

Se consider un numr ntreg n. S se determine dac n este un numr palindrom (cifrele


egal deprtate de extrem sunt identice).

8.3.

Se consider o matrice ptrat de numere reale, A=(aij), i,j=1,...,n. S se determine:


a) Dac matricea este unitate;
b) Dac matricea este superior triunghiular.

8.4.

Se consider un polinom cu coeficieni reali, specificat prin gradul su i vectorul


coeficienilor:
P=a0+a1X+a2X2+ ... +anXn
S se determine:
a) Valoarea polinomului ntr-un punct dat x0;
b) Polinomul obinut prin derivarea polinomului iniial

8.5.

Se consider un polinom de dou variabile, P(X,Y), specificat prin numrul termeni din
polinom, vectorul coeficienilor (cte un coeficient pentru fiecare termen) i o matrice cu
un numr de linii egal cu numrul de termeni i dou coloane reprezentnd puterile celor
dou variabile n termenul corespunztor. S se determine:
a) Valoarea polinomului ntr-un punct dat (x0, y0);
b) Polinomul rezultat prin derivarea polinomului iniial n raport cu variabila X.

8.6.

Se consider un ir de numere reale x1, x2, ..., xn. S se determine:


a) Dac un alt numr real dat, x0, face parte din termenii irului;
b) Toate subirurile irului iniial care sunt ordonate cresctor;
c) Cel mai lung subir al irului iniial ordonat cresctor.

8.7.

Se consider un ir de numere reale x1, x2, ..., xn ordonat cresctor. Considerndu-se n


plus un numr real x0, s se insereze acest numr printre elementele irului iniial, aa nct
el s rmn ordonat i dup inserare.

8.8.

S se elaboreze un algoritm i un program care s sorteze cresctor un ir de numere prin


metoda inserrii: se vor insera pe rnd elementele n ir, astfel nct irul parial obinut
dup inserarea elementului curent s fie de asemenea ordonat cresctor.

8.9.

S se modifice programul de la problema 8.8, astfel nct s permit citirea succesiv a


mai multor expresii, pn cnd primul caracter citit de pe rndul curent este caracterul
punct. Dup fiecare citre se va afia rezultatul, nainte de citirea rndului urmtor.

8.10. Se consider n puncte n plan de coordonate: (x1, y1), (x2, y2), ..., (xn, yn).
a) S se determine dac poligonul cu vrfurile n punctele date este convex sau nu.

b) S se determine: centrul de greutate al poligonului cu vrfurile n punctele date,


perimetrul i aria poligonului.
c) Considerdu-se un alt unct de coordonate (x0, y0), s se determine poziia sa fa de
poligon(n interior, n exterior sau pe perimetru).
8.11. Se consider dou poligoane specificate prin coordonatele vrfurilor n ordine
trigonometric. S se determine dac intersecia acestora este vid sau nu.
8.12. Se consider n plan o mulime de puncte specificat prin coordonatele lor: M = { (x1, y1),
(x2, y2), ..., (xn, yn) }. S se determine nfurtoarea convex a mulimii de puncte.
Indicaie. nfurtoarea convex a unei mulimi M de puncte coplanare este poligonul
convex ce conine ca vrfuri o submulime P puncte din M, P M, iar n interior celelalte
puncte din mulimea M - P.

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