CATEGORII DOCUMENTE |
DOCUMENTE SIMILARE |
|
TERMENI importanti pentru acest document |
|
: | |
Pointeri si tablouri
In C, exista o relatie strinsa intre pointeri si tablouri,
atit de strinsa incit pointerii si tablourile pot fi tratate
simultan. Orice operatie care poate fi rezolvata prin indicierea
tablourilor poate fi rezolvata si cu ajutorul pointerilor. Versiu-
nea cu pointeri va fi in general, mai rapida dar, pentru incepa-
tori, mai greu de inteles imediat.
Declaratia
int a[10]
defineste un tablou de dimensiunea 10, care este un bloc
de 10 obiecte consecutive numite a[0], a[1], , a[9] notatia
a[i] desemneaza elementul deci pozitiile, ale tabloului, numarate
de la inceputul acestuia. Daca pa este un pointer pe un interg,
decalarat ca
int *pa
atunci asignarea
pa = &a[0]
face ca pa sa pointeze pe al 'zero-ulea' element al tabloului a;
aceasta inseamna ca pa contine adresa lui a[0]. Acum asignarea
x = *pa
va copia continutul lui a[0] in x.
Daca pa pointeaza pe un element oarecare al lui a atunci prin
definitie pa+1 pointeaza pe elemmentul urmator si in general
pa-i pointeaza cu i elemente inaintea elementului pointat de
pa iar pa+i pointeaza cu i elemente dupa elementul pointat de
pa. Astfel, daca pa pointeaza pe a[0]
*(pa + 1)
refera continutul lui a[1], pa + i este adresa lui a[i] si
*(pa+i) este continutul lui a[i].
Aceste remarci sint adevarate indiferent de tipul varaiabi-
lelor din tabelul a. Definitia 'adunarii unitatii la un pointer '
si prin extensie, toata aritmetica pointerilor este de fapt
calcularea prin lungimea in memorie a obiectului pointat. Astfel,
in pa+i i este inmultit cu lungimea obiectelor pe care pointeaza
pa inainte de a fi adunate la pa.
Corespondenta intre indexare si aritmetica pointerilor este
evident foarte strinsa. De fapt, referinta la un tablou este
convertita de catre compilator intr-un pointer pe inceputul tablo-
ului. Efectul este ca numele unui tablou este o expresie pointer.
Aceasta are citeva implicatii utile. Din moment ce numele unui
tablou este sinonim cu locatia elementului sau zero, asignarea
pa = &a[0]
poate fi scrisa si
pa = a
Inca si mai surprinzator la prima vedere este faptul ca o
referinta la a[i] poate fi scrisa si ca *(a+i). Evaluind pe
a[i], C il converteste in *(a+i); cele doua forme sint echivalen-
te. Aplicind operatorul & ambilor termeni ai acestei echivalente,
rezulta ca &a[i] este identic cu a+i: a+i adresa elementului al
i-lea in tabloul a. Reciproc: daca pa este un pointer el poate fi
utilizat in expresii cu un indice pa[i] este identic cu *(pa+i).
Pe scurt orice tablou si exprimare de indice pot fi scrise ca
un pointer si offset si orice adresa chiar in aceeasi instructiune
Trebuie tinut seama de o difernta ce exista intre numele
tablou si un pointer. Un pointer este o variabila, astfel ca pa=a
si pa++ sint operatii. Dar, un nume de tablou este o constanta,
nu o variabila: constructii ca a=pa sau a++ sau p=&a sint interzi-
se. Atunci cind se transmite un nume de tablou unei functii,
ceea ce se transmite este locatia de inceput a tabloului. In
cadrul functiei apelate acest fapt argument este o variabila
ca oricare alta astfel incit un argument nume de tablou este un
veritabil pointer, adica o variabila continind o adresa. Ne vom
putea folosi de aceasta pentru a scrie o noua versiune a lui
strlen, care calculeaza lungimea unui sir.
strlen(s) /* returneaza lungimea sirului s */
char *s
Incrementarea lui s este perfect legala deoarece el este o
variabila pointer; s++ nu are efect pe sirul de caractere in
funca care a apelat-o pe strlen, dar incrementeaza doar copia
adresei. Ca parametri formali in definirea unei functii
char s[]
si
char *s;
sint echivalenti; alegerea celui care trebuie scris este deter-
minata in mare parte de expresiile ce vor fi scrise in cadrul
functiei. Atunci cind un nume de tablou este transmis unei
functii, aceasta poate, dupa necesitati s-o interpreteze ca
tablou sau ca pointer si sa-l manipuleze in consecinta. Functia
poate efectua chiar ambele tipuri de operatii daca i se
pare potrivit si corect.
Este posibila si transmiterea catre o functie doar a unei
parti dintr-un tablou prin transmiterea unui pointer pe inceputul
subtabloului. De exemplu, daca a este un tablou;
f(&a[2])
si
f(a + 2)
ambele transmit functiei f adresa elementului a[2] deoarece &a[2]
si a+2 sint expresii pointer care refera al treilea element al lui
a. In cadrul lui f, declarea argumentului poate citi
f(arr)
int arr[];
sau
f(arr)
int *arr;
Astfel, dupa cum a fost conceputa functia f faptul ca argumentul
refera de fapt o parte a unui tablou mai mare nu are consecinte.
5.4. Aritmetica adreselor
Daca p este un pointer, atunci p++ incrementeaza pe p in asa fel
incit t acesta sa pointeze pe elementul urmator indiferent de
tipul obiectelor pointate, iar p+=i incrementeaza pe p pentru
a pointa peste i elemente din locul unde p pointeaza curent.
C este consistent si constant cu aritmetica pointerilor;
pointerii, tablourile si aritmetica adresarii constitue punctul
forte al limbajului. Sa ilustram citeva dintre proprietatile
lui scriind un program pentru alocare de memorie rudimentar (dar
este util in ciuda simplitatii sale exista doua rutine:
alloc(n) returneaza un pointer p pe n pozitii caracter consecu-
tive care poate fi utilizat de catre apelantul lui alloc pentru
alocarea de caractere; free(p) elibereaza memoria facind-o astfel
refolosibila mai tirziu. Rutinele sint 'rudimentare' deoarece
apelurile la free trebuie facute in ordine inversa apelurilor
la alloc. Aceasta inseamna ca memoria gestionata de alloc si
free este o stiva sau o lista prelucrabila in regim LIFP. Biblio-
teca standard C este prevazuta in functii analoage care nu
au atit de multe restrictii iar in capitolul 8 vom da, pentru
demonstratie si alte versiuni. Intre timp se vor ivi multe aplica-
tii care au realmente nevoie de canalul alloc pentru a dispensa
mici portiuni de memorie, de lungimi neprevazute la momente nepre-
vazute.
Cea mai simpla implementare este de a scrie alloc pentru
declararea de parti ale unui tablou mare pe care il vom numi
allocbuf. Acest tablou este propriu lui alloc si free. Lucrind cu
pointeri, nu cu indici in tablou nu este necesar ca vreo alta
rutina sa cunoasca numele tabloului, care poate fi declarat
static, adica local fisierului sursa care sustine pe alloc
si free numele tabloului fiind invizibil in afara acestui fisier.
In implementarile practice tabloul poate chiar sa nu aiba nici
un nume el putind fi obtinut prin cererea catre sistemul de opera-
re a unui pointer pe un bloc de memorie fara nume.
O alta informatie necesara este legata de cit anume din
allocbuf a fost folosit. Vom utiliza un pointer pe urmatorul
element liber, numit allocp. Cind este apelat alloc pentru n
caractere, el verifica daca exista suficient loc eliberat in
allocbuf. Daca astfel alloc returneaza valoarea curenta a lui
allocp (adica inceputul blocului liber) atunci aceasta valoare
esteincrementata cu n in asa fel incit allocp sa pointeze pe
inceputul urmatoarei zone libere. Free(p) pune pur si simplu
pe allocp pe p daca p este in interiorul lui allocbuf.
#define NULL 0 /* val pointerului in caz de eroare */
#define ALLOCSIZE 1000 /* lung spatiului disponibil */
static char allocbuf[ALLOCSIZE]; /* memorie pentru alloc*/
static char *allocp = allocbuf; /*memorarea parti libere*/
char *alloc(n) /* pointer de return pe n caractere */
int n;
else /* nu-i destul loc */
return(NULL)
}
free(p) /* zona de memorie libera pointata de p */
char *p;
Citeva explicatii. In general un pointer poate fi initializat
ca orice alta variabila, desi in mod normal singurele valori
semnificative sint NULL sau o expresie care opereaza adrese
ale unor date in prealabil definite, de tip specificat. Declaratia
static char *allocp = allocbuf;
defineste pe allocp ca fiind un pointer pe caractere si il initia-
lizeaza pentru a-l pointa pe allocbuf care este urmatoarea
pozitie libera atunci cind incepe programul. Aceasta stare ar
putea fi scrisa si astfel
static char *allocp = &allocbuf[0];
deoararece numele tabloului este adresa elementului zero.
Testul
if (allocp + n <= allocbuf + ALLOCSIZE)
verifica daca este suficient loc pentru a satisface cererea pt
n caractere. Daca ezista loc, noua valoare a lui allocp va
fi cel mult mai dincolo de sfirsitul lui allocbuf. Daca cererea
poate fi satisfacuta, alloc retur neaza un pointer normal (obser-
vati declaratia functiei). Daca nu, alloc trebuie sa returneze un
semnal care sa semnifice ca nu exista spatiu liber. Limbajul C
garanteaza ca nici un pointer care pointeaza o data valida nu va
contine zero, asa ca valoarea zero returnata poate fi utilizata
ca semnal de eveniment anormal, nu exista spatiu liber. Se scrie
NULL in loc de zero pt a indica mai clar ca aceasta este o valoare
speciala pt un pointer. In general intregii nu pot fi asignati
pointerilor; zero este u caz special.
Teste ca
if (allocp + n <= allocbuf + ALLOCSIZE)
si
if (p >= allocbuf && p < allocbuf + ALLOCSIZE)
releva citeva fatete importante ale aritmeticii pointerilor.
Mai intii ca in unele situatii pointerii pot fi separati. Daca p
si q pointeaza pe elemente ale aceluiasi tablou, relatii ca <, >,
=, etc lucreaza exact.
p < q
este adevarata, de ex, in cazul in care p pointeaza pe un element
anterior elementului pe care pointeaza q. Relatiile c= si != sint
si ele permise. Orice pointer poate fi testat cu NULL. Dar nu
exista nici o sansa in a compara pointeri in tablouri diferite. In
cazul fericit se va obtine un evident nonsens, indiferent de
masina pe care se lucreaza. Mai poate sa apara situatia nefericita
in care codul va merge pe vreo masina esuind 'misterios' pe altele
In al doilea rind, tocmai s-a observat ca un pointer si un
interg pot fi adunati sau scazuti. Instructiunea
p + n
desemneaza al n-lea obiect dupa cel pointat curent de p. Acest
lucru este adevarat indiferent de tipul obiectelor pe care p a
fost declarat ca pointer. Compilatorul atunci cind il intilneste
pe n, il delaleaza in functie de lungimea obiectelor pe care
pointeaza p, lungime determinata prin declaratia lui p. De exem-
plu, pe PDP11 factorii de scalare sint 11 pentru char, 2 pentru
int si short, 4 pentru long si float si 8 pentru double.
Este valida si scaderea pointerilor: daca p si q
pointeaza pe elementele aceluiasi tablou, p-q este numarul de
elemente dintre p si q. Acest fapt poate fi utilizat pentru a
scrie o noua versiune a lui strlen.
strlen(s) /* returneaza lungimea sirului */
char *s;
Prin declarare, p este initializat pe s, adica sa pointeze pe
primul caracter din s. In cadrul buclei while este examinat pe
care caracter pina se intilneste /0 care semnifica sfirsitul iar
daca while testeaza numai daca expresia este zero este posibila
omiterea testului expilcit iar astfel de bucle sint scrise adesea
while (*p)
p++;
Deoarece p pointeaza pe caractere, p++ face ca p sa avanseze de
fiecare data pe caracterul urmator, iar p-v da numarul de carac-
tere parcurse, adica lungimea sirului. Aritmetica pointerilor este
consistenta: daca am fi lucrat cu float care ocupa mai multa
memorie decit char, si daca p ar fi un pointer pe float, p++ ar
avansa pe urmatorul float. Astfel, vom putea scrie o alta versiune
a lui alloc care pastreaza sa zicem, float in loc de char,
pur si simplu prin schimbarea lui char in float. in cadrul lui
alloc si free. Toate manipularile de pointeri iau automat in
considerare lungimea obiectului pointat in asa fel incit trebuie
sa nu fie alterat.
Alte operatii in afara celor mentionate deja (adunarea sau
scaderea unui pointer cu un intreg, scaderea sau comapararea a doi
pointeri). Toate celelalte operatii arrrtmetice cu pointeri
sint ilegale. Nu este permisa adunarea, impartirea, deplasarea
logica, sau adunarea unui float sau double la pointer.
Politica de confidentialitate | Termeni si conditii de utilizare |
Vizualizari: 1154
Importanta:
Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved