Pointeri si argumente de functii
Datorita faptului ca in C este posibila transmiterea de argumente
unei functii prin "apel prin valoare" nu exista modalitate directa
pentru functia apelata de a altera o variabila in functia apelan-
ta. Ce este de facut atunci cind de fapt, se intentioneaza schim-
barea unui argument obisnuit ? De exemplu, o rutina de sortare
trebuie sa inverseze doua elemente neordonate cu o functie swap.
Nu este suficient sa se scrie
swap(a, b);
unde functia swap este definita ca
swap(x, y) /* GRESIT */
int x, y;
Din cauza apelului prin valoare, swap nu poate afecta argumen-
tele a si b in rutina care o apeleaza.
Din fericire, exista o modalitate de a obtine efectul dorit.
Programul apelant trasmite pointeri pe valorile care trebuie
schimbate.
swap(&a, &b);
Din moment ce operatorul & da adresa unei variabile, &a este un
pointer pe a. In swap insasi, argumentele sint declarate ca fiind
pointeri iar adevaratii operanzi sint accesati prin ei ( prin
pointeri).
swap(px, py) /* interscimba *px si *py */
int *px, *py;
O utilizare comuna a argumentelor de tip poiter se intilneste in
cadrul functiilor care trebuie sa returneze mai mult decit o
singura valoare. (Veti putea obiecta ca swap returneaza doua
valori, noile valori ale argumentelor sale.) Ca un exemplu sa
luam o functie getint care realizeaza inversia la intrare prin
transsformarea unui sir de caractere in valori intregi, un intreg
la fiecare apel, getint trebuie sa returneze valoarea gasita
sau semnul de sfirsit de fisier atunci cind s-a terminat sirul de
caractere de la intrare. Aceste valori trebuie sa fie returnate ca
obiecte separate, pentru indiferent ce valoare este utilizata
pentru EOF aceasta putind fi deasemenea valoarea unui intreg-input
O solutie, care este bazata pe functia input scanf, functie
care o vom descrie in cap7, este de a folosi getint care sa
returneze ca valoare o functie EOF, atunci cind se intilneste
sfirsitul de fisier; orice alta valoare returnata inseamna ca a
fost prelucrat un intreg obisnuit Valoarea numerica a intregului
gasit este returnata printr-un argument care trebuie sa fie
pointer pe un intreg. Aceasta organizare separa starea de
sfirsit de fisier de valorile numerice.
Urmatoarea bucla completeaza un tablou cu intergi prin ape-
luri la get int.
int n, v, array[SIZE]
for (n = 0; n < SIZE && getint(&v) != EOF; n++)
array[n] = v;
Fiecare apel pune pe y pe urmatorul intreg gasit la intrare. De
notat faptul ca este esential a scrie &y in loc de y, ca arg al
lui getint. A scrie doar y constituie eroare de adresare, getint
sustinind ca are de a face cu un pointer propriu zis.
Insasi getint este o modificare evidenta a lui atoi tratata
mai inainte.
getint(pn) /* ia numarul interg -input */
int *pn;
for (*pn = 0; c >= '0' && c <= '9'; c = getch())
*pn = 10 * *pn + c - '0';
*pn *= sign;
if (c != EOF)
ungetch(c);
return(c);
}
Peste tot in getint, *pn este utilizat ca o variabila int ordina-
ra. Deasemenea, am utilizat getch si ungetch ( descrise in cap
4) in asa fel incit caracterul special ( semnalul EOF) care tre-
buie citit sa poata fi restocata la intrare.
Exercitiul 5.1 Scrieti getfloat pentru virgula floatnta analoa-
ga lui getint. Ce tip de valoare returneaza functia getfloat.
5.3. 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.